diff --git a/images/license-github-root-dir.png b/images/license-github-root-dir.png new file mode 100644 index 000000000..7ce10e266 Binary files /dev/null and b/images/license-github-root-dir.png differ diff --git a/images/tutorials/github-new-repo.png b/images/tutorials/github-new-repo.png new file mode 100644 index 000000000..fa1cca8b9 Binary files /dev/null and b/images/tutorials/github-new-repo.png differ diff --git a/index.md b/index.md index eea4cf3fe..24176e3ad 100644 --- a/index.md +++ b/index.md @@ -56,7 +56,7 @@ Community docs Publish your docs ``` -## _new_ Tutorial series - how to create a Python package +## _new_ Tutorial Series: How to Create a Python Package The how to create a Python package tutorial series is being developed by the community now! Join our community review process or watch development of these tutorials in our [Github repo here](https://github.com/pyOpenSci/python-package-guide). @@ -67,18 +67,32 @@ by the community now! Join our community review process or watch development of :gutter: 3 ::::{grid-item} - -:::{card} ✿ Tutorials ✿ +:::{card} ✿ Create a Package Tutorials ✿ :class-card: left-aligned * [What is a Python package?](/tutorials/intro) -* [Make your code installable](/tutorials/1-installable-code) +* [Make your code installable](/tutorials/installable-code) * [Publish your package to (test) PyPi](/tutorials/publish-pypi) -* *How to add a README and LICENSE to support publication (coming next!)* -* *How to add metadata to a pyproject.toml file for publication to PyPI.* +* [Publish your package to conda-forge](/tutorials/publish-conda-forge) + +::: +:::: + +::::{grid-item} +:::{card} ✿ Package Metadata Tutorials ✿ +:class-card: left-aligned -_The third lesson is currently under review in our [GitHub Repo here](https://github.com/pyOpenSci/python-package-guide/pulls). It will be live by the end of Feb 2024_ +* [How to add a README file](/tutorials/add-readme) +* [How to add metadata to a pyproject.toml file for publication to PyPI.](/tutorials/pyproject-toml.md) + +::: +:::: + +::::{grid-item} +:::{card} ✿ Packaging Tool Tutorials ✿ +:class-card: left-aligned +* [Introduction to Hatch](/tutorials/get-to-know-hatch) ::: :::: diff --git a/tutorials/add-license-coc.md b/tutorials/add-license-coc.md new file mode 100644 index 000000000..6b17bfad1 --- /dev/null +++ b/tutorials/add-license-coc.md @@ -0,0 +1,177 @@ +# Add a LICENSE & CODE_OF_CONDUCT to your Python package + +In the [previous lesson](add-readme) you: + + Created a basic `README.md` file for your scientific Python package + + Learned about the core components that are useful to have in a README file. + +:::{admonition} Learning objectives +:class: tip + +In this lesson you will learn: + +1. How to select and add a `LICENSE` file to your package repository with a focus on the GitHub interface. +2. How to add a `CODE_OF_CONDUCT` file to your package repository. +3. How you can use the Contributors Covenant website to add generic language as a starting place for your `CODE_OF_CONDUCT`. +::: + +## Add a LICENSE file to your project directory + +A license contains legal language about how users can use and reuse your software. You should include a LICENSE file in your project directory that specifies the license that you choose for your package. + +We suggest that you use a permissive license that accommodates the other most commonly used licenses in the scientific Python ecosystem (MIT[^mit] and BSD-3[^bsd3]). If you are unsure, use MIT given it's the generally recommended +license on [choosealicense.com](https://choosealicense.com/). + +:::{admonition} Licenses for the scientific Python ecosystem +[We discuss licenses for the scientific Python ecosystem in more detail here in our guidebook.](permissive-license) +::: + +### Where should the LICENSE file live & how do you add it? + +Your `LICENSE` file should be placed at the root of your package's repository. +When you add the LICENSE at the root, GitHub will automagically discover it and +provide users with a direct link to your license file within your GitHub +repository. + +:::{figure-md} github-coc-readme-license-tabs +Image showing the GitHub repository for SunPy an accepted pyOpenSci package. + +Notice at the top of the +README portion of the GitHub landing page, there are three tabs directly linking to the README file which is visible, the CODE_OF_CONDUCT file and one that specifies +the license that SunPy uses. These files are discovered by GitHub because they +are placed in the root of the project directory using standard naming conventions. +::: + +There are several ways to add a license file: + +1. When you create a new repository on GitHub, it will ask you if you wish to add a `LICENSE` file at that time. If you select yes, it will create the file for you. +2. You can add a license through the GitHub gui following the [ instructions here](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository). +3. You can add the file manually like we are doing in this lesson. + +:::{tip} +If you completed the past lessons including + +1. [Making your code installable](installable-code.md) and +2. [publishing your package to PyPI](publish-pypi.md) + +then you already have a **LICENSE** file containing text for the MIT license in your Python package. Thus you can skip to the next section of this tutorial which walks you through adding a CODE_OF_CONDUCT. + +If you don't yet have a **LICENSE** file in your directory, then continue reading. +::: + +### How to add a LICENSE to your existing GitHub repository + +If you don't already have a LICENSE file, and you are not yet using a platform such as GitHub or GitLab, then you can create a license file by + +1. Create a new file called LICENSE. If you are using a shell you can use: + +``` +# Create a license file in your shell +> touch LICENSE +``` + +1. Go to [choosealicense.com](https://choosealicense.com/) +2. Select permissive license +3. It will suggest that you use the [MIT license](https://choosealicense.com/licenses/mit/). +4. Copy the license text that it provides into your LICENSE file that you created above. + +:::{admonition} An overview of LICENSES in the scientific Python ecosystem +:class: note + +In the pyOpenSci [packaging guidebook](../documentation/repository-files/license-files), we provide an overview of license in the scientific Python ecosystem. We review why license files are important, which ones are most commonly used for scientific software and how to select the correct license. + +If you want a broad overview of why licenses are important for protecting open source software, [check out this blog post that overviews the legal side of things.](https://opensource.guide/legal/#just-give-me-the-tldr-on-what-i-need-to-protect-my-project) +::: + +::::::{dropdown} Instructions for adding a license files within the GitHub interface +:color: primary + +:::::{tab-set} + +::::{tab-item} Add license: new GitHub repository + +When you create a new GitHub repository you can add a license +through the GitHub interface. + +:::{figure-md} github-new-repo + +Screenshot of the create new repository interface that GitHub provides. The elements of this are the owner and repository name for the new repo. Below that you can add a description of the repo. Below that you can set it to be public or private. At the bottom of the interface there is an Add a README checkbox where it will add a blank readme file for you. At the very bottom there is a line to add a .gitignore file and another to choose a license. + +Image showing the GitHub interface that allows you to add a LICENSE and README file when you create a new repository. +::: +:::: + +::::{tab-item} Add License: Existing GitHub repository + +If you already have a GitHub repository for your package, then you can add a LICENSE using the GitHub interface by adding a new file to the repo. + +- Follow the instructions to select and add a license to your repository on the [GitHub LICENSE page](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository) . +- Once you have added your LICENSE file, be sure to sync your git local repository with the repository on GitHub.com. This means running `git pull` to update your local branch. + +:::{figure-md} view-license +sdfsdfsd nasdfjsdf + +You can also view a summary of the license on its GitHub landing page.``` +::: +:::: + +::::: +:::::: + +(add-coc)= +## Add a CODE_OF_CONDUCT file to your repo + +Now that you have added a LICENSE to your project, you are ready to add a `CODE_OF_CONDUCT.md` to your package directory. The `CODE_OF_CONDUCT.md` should be placed at the root of your project directory, similar to the LICENSE file,. + +A `CODE_OF_CONDUCT` file is critical to supporting your community as it +grows. The `CODE_OF_CONDUCT`: + +1. Establishes guidelines for how users and contributors interact with each other and you in your software repository. +2. Identifies negative behaviors that you don't want in your interactions. + +You can use your code of conduct as a tool that can be referenced when moderating challenging conversations. + +If you are unsure of what language to add to your `CODE_OF_CONDUCT` +file, we suggest that you adopt the [contributor covenant language](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) as a starting place. + +[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-2.1-4baaaa.svg)](#) + +### Add your CODE_OF_CONDUCT file + +- Add a `CODE_OF_CONDUCT.md` file to your repository if it doesn't + already exist. +- Visit the [contributor covenant website](https://www.contributor-covenant.org/) and add [the markdown version of their code of conduct](https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md) to your `CODE_OF_CONDUCT.md` file. + +:::{admonition} Additional Code of Conduct resources +:class: note + +- [ Guide: `CODE_OF_CONDUCT.md` files](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-code-of-conduct-to-your-project) +- [pyOpenSci package guide `CODE_OF_CONDUCT.md` overview](https://www.pyopensci.org/python-package-guide/documentation/repository-files/code-of-conduct-file.html) + +::: + +## Wrap up + +In this lesson and the [last lesson](add-readme), you have added a: + +- `README` file; +- `LICENSE` file and a +- `CODE_OF_CONDUCT` file. + +These are fundamental files needed for every scientific Python package +repository. These files help users understand how to use your package and +interact with package maintainers. In the upcoming +lessons, you will: + +- [Flesh out your `pyproject.toml` file](pyproject-toml) to support building + and publishing your package on PyPI. +- Publish a new version of your Python package to (Test) PyPI to preview the + updated metadata landing page. + +--- + +## Footnotes + +[^mit]: https://opensource.org/license/mit/ +[^bsd3]: https://opensource.org/license/bsd-3-clause/ diff --git a/tutorials/add-readme.md b/tutorials/add-readme.md index 732b8e32b..ed2d61844 100644 --- a/tutorials/add-readme.md +++ b/tutorials/add-readme.md @@ -187,7 +187,10 @@ Short description here using non-technical language that describes what your pac ## How to install pyosPackage - +:::{todo} +- when i add more to the pyos package this can use that readme> +::: + To install this package run: `pip install pyosPackage` diff --git a/tutorials/1-installable-code.md b/tutorials/installable-code.md similarity index 98% rename from tutorials/1-installable-code.md rename to tutorials/installable-code.md index a25be5924..ff2250363 100644 --- a/tutorials/1-installable-code.md +++ b/tutorials/installable-code.md @@ -331,10 +331,9 @@ file. [Learn more about the pyproject.toml format here.](../package-structure-code/pyproject-toml-python-package-metadata) ::: -:::{todo} + You will learn more about the `pyproject.toml` format in the -[next lesson when you add additional metadata / information to this file.](5-pyproject-toml.md) -::: +[next lesson when you add additional metadata / information to this file.](pyproject-toml.md) - Open up the `pyproject.toml` file that Hatch created in your favorite text editor. It should look something like the example below. @@ -614,13 +613,12 @@ In the upcoming lessons you will: * Add more metadata to your `pyproject.toml` file to support PyPI publication. * learn how to publish to **conda-forge** from **PyPI**. -:::{todo} -This is the content with links once the links are live we can uncomment this and remove the unlinked content above! -* Add a [README file](2-add-readme.md) and [LICENSE](4-add-license-file.md) to your package + +* Add a [README file](add-readme.md) and [LICENSE](add-license-coc.md) to your package * [Add more metadata to your `pyproject.toml`](5-pyproject-toml.md) file to support PyPI publication. -* [Learn how to build your package distribution](6-publish-pypi.md) files (**sdist** and **wheel**) and publish to **test PyPI**. -* Finally you will learn how to publish to **conda-forge** from **PyPI**. -::: +* [Learn how to build your package distribution](publish-pypi) files (**sdist** and **wheel**) and publish to **test PyPI**. +* Finally you will learn how to [publish to **conda-forge**](publish-conda-forge) from **PyPI**. + ## Footnotes diff --git a/tutorials/intro.md b/tutorials/intro.md index 35f2f1718..2ce3c94cc 100644 --- a/tutorials/intro.md +++ b/tutorials/intro.md @@ -33,19 +33,21 @@ Get to know Hatch :::{toctree} :hidden: -:caption: Create a Python package +:caption: Create a Python Package What is a Python package? -Make your code installable <1-installable-code> +Make your code installable Publish to PyPI Publish to conda-forge ::: :::{toctree} :hidden: -:caption: Project Metadata +:caption: Citation, License & Project metadata Add README file +Add a license & COC +Update metadata ::: :::{admonition} Learning Objectives @@ -345,5 +347,5 @@ The elements above are also important for future maintenance of your package. In In future lessons you will learn more about the infrastructure around a published Python package that makes it both easier to maintain, easier for others to contribute to and easier for other scientists to use. However, first we want to get you to your initial goal of publishing a Python package. In this next lesson you will learn how to create a basic installable Python package. -Make your code pip installable <1-installable-code> +Make your code pip installable ::: diff --git a/tutorials/publish-conda-forge.md b/tutorials/publish-conda-forge.md index 29d2d248a..0a8d4cc07 100644 --- a/tutorials/publish-conda-forge.md +++ b/tutorials/publish-conda-forge.md @@ -2,7 +2,7 @@ In the previous lessons, you've learned: -1. How to [create the most basic version of a Python package](1-installable-code.md). This entailed making your code installable. +1. How to [create the most basic version of a Python package](installable-code.md). This entailed making your code installable. 2. [How to publish your Python package to PyPI](publish-pypi) 3. How to add a `README` and `LICENSE` file to your package 4. How to setup your `pyproject.toml` file with all of the metadata that PyPI requires and also metadata that will be helpful for users to find your package. @@ -468,7 +468,7 @@ Review the pull request. If all tests are passing, you can merge it. Shortly aft If you have walked through this entire tutorial series you have now: 1. Understand [what a Python package is ](intro.md) -2. Know how to [make your code installable](1-installable-code.md) into Python environments +2. Know how to [make your code installable](installable-code.md) into Python environments 3. Know how to create a pyproject.toml file, a README file, and a License and code of conduct. 4. Know how to [publish your package to PyPI](publish-pypi.md) and 5. Know how to publish your package to conda-forge diff --git a/tutorials/publish-pypi.md b/tutorials/publish-pypi.md index 6b622c26d..2992cae46 100644 --- a/tutorials/publish-pypi.md +++ b/tutorials/publish-pypi.md @@ -1,10 +1,11 @@ # Publish your Python package to PyPI :::{todo} -* emphasize that we recommended the trusted publisher GitHub action for most maintainers -* Make sure they add /dist to their .gitignore file. We have not discussed GitHub workflows anywhere yet. Where does that fit? -* https://hatch.pypa.io/latest/intro/#existing-project <- hatch will migrate from setup.py for you - if we go with hatch then we may want to add this to the installable code lesson -* Should we install hatch with pipx? +- emphasize that we recommended the trusted publisher GitHub action for most maintainers +- Make sure they add /dist to their .gitignore file. We have not discussed GitHub workflows anywhere yet. Where does that fit? +- https://hatch.pypa.io/latest/intro/#existing-project <- hatch will migrate from setup.py for you - if we go with hatch then we may want to add this to the installable code lesson +::: + ```bash pipx install hatch @@ -37,13 +38,8 @@ using [Grayskull](https://conda.github.io/grayskull/). You will learn how to publish to conda-forge in a future lesson. -:::{todo} -Fix this link once the lesson is published. - -You will learn how to publish to conda-forge in the [next lesson](7-publish-conda-forge.md). -::: +You will learn how to publish to conda-forge in the [next lesson](publish-conda-forge). -::: :::{figure-md} build-workflow-tutorial Graphic showing the high level packaging workflow. On the left you see a graphic with code, metadata and tests in it. Those items all go into your package. An arrow to the right takes you to a build distribution files box. Another arrow to the right takes you to a publish to PyPI box which has an arrow containing sdist and wheel that notes those files go to PyPI for hosting. From PyPI is an arrow containing sdist since you can then connect to conda-forge for an automated build that sends distributions from PyPI to conda-forge. @@ -51,7 +47,6 @@ You will learn how to publish to conda-forge in the [next lesson](7-publish-cond You need to build your Python package in order to publish it to PyPI (or Conda). The build process organizes your code and metadata into a distribution format that can be uploaded to PyPI and subsequently downloaded and installed by users. ::: - ## Test PyPI vs PyPI There are two "warehouses" that you can use to publish @@ -76,13 +71,13 @@ to PyPI. You need to: 1. **Create an account on (test) PyPI**: You will need to create a PyPI account and associated token which provides permissions for you to upload your package. 1. **Publish to PyPI using `hatch publish`** - In a future lesson, you will learn how to create an automated GitHub action workflow that publishes an updated version of your package to PyPI every time you create a GitHub release. :::{admonition} Learn more about building Python packages in our guide :class: tip + - [Learn more about what building a Python package is](../package-structure-code/python-package-distribution-files-sdist-wheel) - [Learn more about package distribution file that PyPI needs called the wheel](#python-wheel) - [Learn more about the package distribution file that conda-forge will need on PyPI called the sdist (source distribution)](#python-source-distribution) @@ -139,7 +134,6 @@ six 1.16.0 tzdata 2023.4 ``` - At any time you can exit the environment using `exit`. ```bash @@ -154,13 +148,13 @@ pyosPackage (☊ main) [✎ ×1 ] is 📦 v0.1.4 via 🐍 pyenv took 43s ➜ ``` - ### Hatch and environments Behind the scenes when hatch creates a new virtual environment, by default it uses venv[^venv] which is the default environment management tool that comes with Python installations. Hatch will: + 1. Create a new virtualenv (venv) that is located on your computer. 2. Install your package into the environment in editable mode (similar to `pip install -e`). This means it installs both your project and your project's dependencies as declared in your pyproject.toml file. @@ -173,7 +167,7 @@ Once you have your development environment setup, you are ready to build your pa You will use Hatch as a **Front end** tool that builds your package's sdist and wheel using the [hatchling](https://hatch.pypa.io/latest/) build back-end. -The hatchling build back-end is used because you declared it in your pyproject.toml file in the [previous lesson](1-installable-code). +The hatchling build back-end is used because you declared it in your pyproject.toml file in the [previous lesson](installable-code). To build your package run `hatch build`: @@ -192,10 +186,8 @@ You can learn more about building in the [build page of our packaging guide](../package-structure-code/python-package-distribution-files-sdist-wheel). ::: -:::{todo} The sdist is important if you wish to [publish -your package to conda-forge](7-publish-conda-forge) which you will learn about in a later lesson. -::: +your package to conda-forge](publish-conda-forge). You will learn about this in a later lesson. :::{todo} ➜ hatch build @@ -232,7 +224,7 @@ Example: `pyosPackage_yourNameHere`. Show them how to do this 1. update the project-name in the pyproject.toml file -2. update the module repo directory to be the same +2. update the module repository directory to be the same ::: :::{figure-md} build-workflow-tutorial @@ -242,7 +234,6 @@ Before you try to upload to test PyPI, check to see if the name of your package the search box at the top of the test PyPI website. ::: - :::{admonition} Setup 2-factor (2FA) authentication 2-factor authentication is a secure login process that allows you to @@ -268,12 +259,11 @@ It's ideal to create a package-specific token. When you create an account wide t ### Follow the steps below to create your token. -* Login to test PyPI and go to your account settings -* Scroll down to the **API tokens** section -* Click on the **Add API Token** button - * If you are new to using PyPI and don't have any packages there yet, OR if you have other packages on PyPI but are uploading a new package, you will need to create an account-wide token. -* When you create your token, be sure to copy the token value and store it in a secure place before closing that browser. - +- Login to test PyPI and go to your account settings +- Scroll down to the **API tokens** section +- Click on the **Add API Token** button + - If you are new to using PyPI and don't have any packages there yet, OR if you have other packages on PyPI but are uploading a new package, you will need to create an account-wide token. +- When you create your token, be sure to copy the token value and store it in a secure place before closing that browser. Your token should look something like this: @@ -286,12 +276,12 @@ It should start with `pypi` followed by a dash and a bunch of characters. Once you have your token, you are ready to publish to PyPI. -* Run `hatch publish -r test` +- Run `hatch publish -r test` `-r` stands for repository. In this case because you are publishing to test-PyPI you will use `-r test`. Hatch will then ask for a username and credentials. -* Add the word `__token__` for your username. This tells Test PyPI that you are using a token value rather than a username. -* Paste your PyPI token value in at the `Enter your credentials` prompt: +- Add the word `__token__` for your username. This tells Test PyPI that you are using a token value rather than a username. +- Paste your PyPI token value in at the `Enter your credentials` prompt: ```bash ❯ hatch publish -r test @@ -321,7 +311,6 @@ landing page for your newly uploaded package. This is an example landing page for the pyosPackage that was just uploaded. Notice at the top of the page there is instruction for how to install the package from test PyPI. You can simply copy that code and use it to install your package from testPyPi locally. ::: - As an example, [check out our pyOpenSci pyosPackage landing page on test PyPI](https://test.pypi.org/project/pyosPackage/). Notice that the page has information about the current package version and also installation instructions as follows: @@ -335,9 +324,9 @@ testPyPI as a permanent way to install your package. Test PyPi is a perfect plac ### Time to install your package -* On your computer, activate the development environment that -you wish to install your newly published package in. -* Run the installation instructions for your package from test PyPI. +- On your computer, activate the development environment that + you wish to install your newly published package in. +- Run the installation instructions for your package from test PyPI. ::::{tab-set} @@ -348,9 +337,11 @@ you wish to install your newly published package in. > pip install -i https://test.pypi.org/simple/ youPackageNameHere > conda list ``` + ::: :::{tab-item} venv mac / Linux + ```bash > hatch shell > pip install -i https://test.pypi.org/simple/ youPackageNameHere @@ -363,14 +354,12 @@ you wish to install your newly published package in. In this lesson you are using Hatch and hatchling to create, build and publish your Python Package. [Click here to learn about other packaging tools in the ecosystem.](../package-structure-code/python-package-build-tools.md) ::: - - - - +::: ## Package-specific token vs trusted publisher @@ -412,3 +401,4 @@ You will learn how to do that in the next lesson. ## Footnotes [^venv]: https://docs.python.org/3/library/venv.html +``` diff --git a/tutorials/pyproject-toml.md b/tutorials/pyproject-toml.md new file mode 100644 index 000000000..f90f72037 --- /dev/null +++ b/tutorials/pyproject-toml.md @@ -0,0 +1,527 @@ +# Make your Python package PyPI ready - pyproject.toml + +In [the installable code lesson](2-installable-code), you learned how to add the bare minimum information to a `pyproject.toml` file to make it installable. You then learned how to [publish that bare minimum version of your package to PyPI](publish-pypi.md). + +Following that you learned how to add a: +* [README.md](add-readme) +* [LICENSE](add-license-coc) and +* [CODE_OF_CONDUCT](add-coc) + +to the root of your project directory. + +To enhance the visibility of your package on PyPI and provide more information +about its compatibility with Python versions, project development status, and +project maintainers, you should add additional metadata to your `pyproject.toml` +file. This +lesson will guide you through the process. + + +:::{admonition} Learning Objectives +:class: tip + +In this lesson you will learn: + +1. More about the `pyproject.toml` file and how it's used to store different types of metadata about your package +1. How to declare information (metadata) about your project to help users find and understand it on PyPI. + +If you wish to learn more about the `pyproject.toml` format, [check out this page. ](../package-structure-code/pyproject-toml-python-package-metadata.md) +::: + +:::{dropdown} Click for lesson highlights +:color: secondary +:icon: unlock + +When creating your pyproject.toml file, consider the following: + +1. There are only two required metadata tables that you need to install and publish your Python package: + * **[build-system]** + * **[project]**. +2. The **[project]** table stores your package's metadata. Within the **[project]** table, There are only two _required_ fields: + * **name=** + * **version=** +3. You should add more metadata to the `[project]` table as it will make it easier for users to find your project on PyPI. And it will also make it easier for installers to understand how to install your package. +3. When you are adding classifiers to the **[project]** table, only use valid values from [PyPI’s classifier page](https://PyPI.org/classifiers/). An invalid value here will raise an error when you build and publish your package on PyPI. +4. There is no specific order for tables in the `pyproject.toml` file. However, fields need to be placed within the correct tables. For example `requires =` always need to be in the **[build-system]** table. +5. We suggest that you include your **[build-system]** table at the top of your `pyproject.toml` file. +::: + + +## What is a pyproject.toml file? + +The `pyproject.toml` file is a human and machine-readable file that serves as the primary configuration file for your Python package. + +The TOML format can be compared to other structured formats such as`.json`. However, the TOML format was designed to be easier to read for humans. + +:::{tip} +[Building your package](build-package) is the step that created the distribution files that are required for you to publish to PyPI. +::: + + +### About the .toml format + +The **pyproject.toml** file is written in [TOML (Tom's Obvious, Minimal Language) format](https://toml.io/en/). TOML is an easy-to-read structure that is founded on key/value pairs. Each section in the **pyproject.toml** file contains a `[table identifier]`. + +Below you can see the `[build-system]` table. Within +that table there are two required key/value pairs. + +`requires =` is the key and the value is `["hatchling"]` within the `[build-system]` array specified by square brackets []. + +```toml +[build-system] # <- this is a table +requires = ["hatchling"] +# The build backend defines the tool that should be used to build your package distribution files. +build-backend = "hatchling.build" +``` + + +### What is the pyproject.toml used for? + +The pyproject.toml file tells your build tool: + +- What build backend to use to build your package (we are using `hatchling` in this tutorial but there are [many others to chose from](build-backend-options)). +- How and where to retrieve your package's version: + - **statically** where you declare the version `version = "0.1.0"` or + - **dynamically** where the tool looks to the most recent tag in your history to determine the current version. +- What dependencies your package needs +- What versions of Python your package supports (important for your users). + +The `pyproject.toml` file also makes it easy for anyone browsing your GitHub +repository to quickly understand your package's structure such as: + +- How your package is built, +- What Python versions and operating systems it supports +- What it does, +- Who maintains it + +Finally, it is also often used to configure tools such as static type checkers (e.g. mypy), linters (e.g. black, ruff), and other tools. + +:::{tip} +Check out the [PyPA documentation](https://packaging.python.org/en/latest/tutorials/packaging-projects/#creating-pyproject-toml) if you are interested in setting build configurations for other tools. + +Note that some build tools may deviate in how they store project metadata. As such you may want to refer to their documentation if you decide to use a tool other than Hatch and hatchling. We have selected hatchling and hatch as our tool of choice for this tutorial as it adheres to PyPA rules and guidelines. + +::: + +### What is the metadata for? + +The pyproject.toml file is the file that your build tool uses to populate +a `METADATA` that is included in your Python distribution files that get published to PyPI. + +This `METADATA` file is then used by PyPI to populate your package's PyPI landing page and help users filter through the tens of thousands of packages published there. + +```{figure} ../images/python-build-package/pypi-metadata-classifiers.png +:scale: 50 % +:align: center +:alt: Image showing the left side bar of PyPI for the package xclim. The section at the top says Classifier. Below there is a list of items including Development status, intended audience, License, natural language, operating system, programming language and topic. Below each of those sections are various classifier options." width="300px"> + +When you add the classifier section to your pyproject.toml +and your package is built, the build tool organizes the metadata into a format that PyPI can understand and +represent on your PyPI landing page. These classifiers also allow users to sort through packages by version of python they support, categories and more. +``` + +:::{tip} A more indepth overview of pyproject.toml files + +[Our guidebook page has a more in depth overview of this file](../package-structure-code/pyproject-toml-python-package-metadata/) +::: + + +## Time to update your pyproject.toml file + +In the last lesson, you created a bare-bones pyproject.toml +file that contained the core elements needed to build your +package: + +1. A `[build-system]` table where you defined your project's backend build tool (`hatchling`) +2. A `[project]` table where you defined your project's version and name. + +The `pyproject.toml` file that you created, looked like this: + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pyospackage" +version = "0.1.0" +``` + +Your next step is to add additional recommended metadata fields that will both +help users find your package on PyPI and also better describe the scope of your package. Once you add this metadata, you don't have to do it again. These metadata fields will only be updated periodically when you do something such as: + +- drop a package dependency +- modify what Python versions your package supports. + +:::{admonition} More on hatchling +:class: tip + +The documentation for the hatchling back-end is [here](https://hatch.pypa.io/latest/config/metadata/) +::: + +## Add metadata to your pyproject.toml `[project]` table + +So far you have a project name and a version in the `[project]` table. + +```toml +[project] +name = "pyospackage" +version = "0.1.0" + +``` + +Let's add the following to your table: + +- A **description** of your package. This should be a single line and should briefly describe the goal of your package using non technical terms if as all possible! +- package **authors** +- package **maintainers** + +When you add authors and maintainers you need to use a format that will look like a Python list with a dictionary within it: + +`authors = [{ name = "Firstname lastname", email = "email@pyopensci.org" }]` + +If you have two authors you can add them like this: + +`authors = [{ name = "Firstname lastname", email = "email@pyopensci.org" }, { name = "Firstname lastname", email = "email@pyopensci.org" }]` + +:::{admonition} Author names & emails +:class: note + +There is a quirk with PyPI for authors that have names but not emails in the pyproject.toml. If you don't have emails for everyone, we suggest that you only add names. + +::: + +Your `pyproject.toml` file now should look like the example below. It is OK if you only have 1 author and the same author is also maintainer of your package: + +```toml +[project] +name = "pyospackage" +version = "0.1.0" +description = "Tools that update the pyOpenSci contributor and review metadata that is posted on our website" +authors = [{ name = "Firstname lastname", email = "email@pyopensci.org" }] +maintainers = [{ name = "Firstname lastname", email = "email@pyopensci.org" }, { name = "Firstname lastname", email = "email@pyopensci.org" }] +``` + +:::{dropdown} Learn More: What's the difference between author and maintainer in open source? +:color: secondary + +When adding maintainers and authors, you may want to think about the difference between the two. + +Authors generally include people who: +* originally created / designed developed the package and +* people who add new functionality to the package. + +Whereas maintainers are the people that are currently, actively working on the project. It is often the case that there is overlap in authors and maintainers. As such these lists may be similar or the same. + +A good example of when the lists might diverge is sometimes you have a package where an initial author developed it and then stepped down as a maintainer to move on to other things. This person may continue to be considered an author but no longer actively maintains the package. + +It is important to note that there are many ways to define author vs maintainer and we don't prescribe a single approach in this tutorial. + +However, we encourage you to consider carefully, for PyPI publication, who +you want to have listed as authors and maintainers on your PyPI landing page. +::: + + +### Add classifiers to your metadata + +Next you will add classifiers to your `pyproject.toml` file. The value for each classifier that you add to your `pyproject.toml` file must come from the list of [PyPI accepted classifier values found here](https://PyPI.org/classifiers/). Any deviations in spelling and format will cause issues when you publish to PyPI. + +:::{admonition} What happens when you use incorrect classifiers? +:class: tip + +If you do not [use standard classifier values](https://PyPI.org/classifiers/), when you try to publish your package on PyPI it will be rejected. 😔 Don't worry if PyPI rejects you on your first try! It has happened to all of us. +::: + +Review that list and add items below to your `pyproject.toml` file: + +- development status +- intended audiences +- topic +- license and +- programming language support + +The classifier key should look something like the example below. A few notes: + +- Your classifier values might be different depending upon the license you have selected for your package, your intended audience, development status of your package and the Python versions that you support +- You can add as many classifiers as you wish as long as you use the [designated PyPI classifier values](https://PyPI.org/classifiers/). + +```toml +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +``` + +Note that while classifiers are not required in your `pyproject.toml` file, they will help users find your package. As such we strongly recommend that you add them. + +### Add package dependencies + +Next add your dependencies table to the project table. +Dependencies represent the Python packages that your package requires to run. Similar to the build-system above, dependencies +get added in an array (similar to a Python list) structure. + +```toml +dependencies = ["numpy", "requests", "pandas", "pydantic"] + +``` + +:::{admonition} Pin dependencies with caution +Pinning dependencies refers to specifying a specific version of a dependency like this `numpy == 1.0`. In some specific cases, you may chose to pin or specify a lower or upper bound of a specific package. + +You can declare a lower bound using syntax like this: + +`ruamel-yaml>=0.17.21` + +[Learn more about various ways to specify ranges of package versions here.](https://packaging.python.org/en/latest/specifications/version-specifiers/#id5) + +Note that unless you are building an application, you want to be cautious about pinning dependencies. This is because +users will be installing your package into various environments. A pinned dependency can make resolving an environment more challenging to resolve. As such only pin dependencies to a specific version or bound if you absolutely need to do so. + +One build tool that you should be aware of that pins dependencies by default is Poetry. [Read more about how to safely add dependencies with Poetry, here.](../package-structure-code/python-package-build-tools.html#challenges-with-poetry) +::: + +### Requires-python + +Finally, add the `requires-python` field to your `pyproject.toml` `[project]` table. The `requires-python` field, helps pip understand the lowest version of Python that you package supports when it's installed. It is thus a single value. + +`requires-python = ">=3.10"` + +### Your current pyproject.toml file + +Once you have dependencies declared in the `pyproject.toml` file, your build +tool will know to install them when your package is installed using pip. + +The project table of your `pyproject.toml` file should now look something like the example below. + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pyospackage" +version = "0.1.0" +description = "Tools that update the pyOpenSci contributor and review metadata that is posted on our website" +authors = [{ name = "Firstname lastname", email = "email@pyopensci.org" }] +maintainers = [{ name = "Firstname lastname", email = "email@pyopensci.org" }, { name = "Firstname lastname", email = "email@pyopensci.org" }] + +requires-python = ">=3.10" + +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + +dependencies = ["ruamel-yaml>=0.17.21", "requests", "python-dotenv", "pydantic"] + +``` + +### Add your README and license + +In the previous lessons, you added both a [README.md](add-readme) file and a [LICENSE](add-license-coc) to your package repository. +Once you have those files, you can add them to your pyproject.toml file as +links following the example below. + + +```toml +readme = "README.md" +license = {file = 'LICENSE'} +``` + +## Add the `[project.urls]` table + +Finally, add the project.urls table to your +pyproject.toml file. + +`project.urls` contains links that are relevant for your project. You might want to include: + +- **Homepage:** A link to your published documentation for your project. If you are working through this tutorial, then you may not have this link yet. That's ok, you can skip it for the time being. +- **Bug reports:** a link to your issues / discussions or wherever you want users to report bugs. +- **Source:** the GitHub / GitLab link for your project. + +``` +[project.urls] # Optional +"Homepage" = "https://www.pyopensci.org" +"Bug Reports" = "https://github.com/pyopensci/pyosmeta/issues" +"Source" = "https://github.com/pyopensci/pyosmeta/" +``` + +:::{tip} +There are many other urls that you can add here. Check out the [README file here for an overview](https://github.com/patrick91/links-demo). +::: + +## Putting it all together - your completed pyproject.toml file + +Below is an example of a complete `pyproject.toml` file that +is commented with all of the sections we discussed above. + +```toml +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pyosmeta" +version = "0.1.0" +description = "Tools that update the pyOpenSci contributor and review metadata that is posted on our website" +authors = [{ name = "Firstname lastname", email = "email@pyopensci.org" }] + +maintainers = [ + { name = "firstname lastname", email = "admin@pyopensci.org" }, +] + +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3 :: Only", # BE sure to specify that you use python 3.x + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] + +dependencies = ["ruamel-yaml>=0.17.21", "requests", "python-dotenv", "pydantic"] + +requires-python = ">=3.10" +readme = "README.md" +license = { text = "MIT" } + +[project.urls] # Optional +"Homepage" = "https://www.pyopensci.org" +"Bug Reports" = "https://github.com/pyopensci/pyosmeta/issues" +#"Funding" = "" +"Source" = "https://github.com/pyopensci/pyosmeta/issues" +``` + + +## Appendix - A fully commented pyproject.toml file + +Below is a fully commented pyproject.toml file if you want to use it for reference. + +```toml +# You can delete all of the comments once you have created your own pyproject.toml file. + +# The build system table. Here we use hatchling as the build back end tool. +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +# The [project] section contains your package's metadata +# notice that the version is setup to be dynamically generated using dynamic=[“version”] + +[project] +name = "pyospackage" +# dynamic = ["version"] # you will learn how to dynamically set the version in a future lesson +version = "0.1.0" # manually assign version (not preferred) +description = "Tools that update the pyOpenSci contributor and review metadata that is posted on our website" +authors = [{ name = "Firstname lastname", email = "email@pyopensci.org" }] + +# maintainers section is optional but suggested. +maintainers = [ + { name = "firstname lastname", email = "admin@pyopensci.org" }, # Optional +] + +# Classifiers have set values - be sure to only use classifier values from the +# PyPI page here: https://PyPI.org/classifiers/ + +classifiers = [ + # How mature is this project? Common values are + # 3 - Alpha + # 4 - Beta + # 5 - Production/Stable + "Development Status :: 4 - Beta", + + # Indicate who your project is intended for + "Intended Audience :: Developers", + "Topic :: Software Development :: Build Tools", + + # Pick your license (using syntax from the classifier page). We suggest MIT, BSD3 or Apache if you are corporate + "License :: OSI Approved :: MIT License", + + # Specify the Python versions ensuring that you indicate you support Python 3. + # this is only for PyPI and other metadata associated with your package - for your users to see + "Programming Language :: Python :: 3 :: Only", # BE sure to specify that you use python 3.x + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", +] + + +dependencies = ["xarray", "requests"] +# This is the metadata that pip reads to understand what versions your package supports +requires-python = ">=3.10" +readme = "README.md" +license = { FILE = LICENSE } + +# Add urls for your home page, issue tracker and source code +[project.urls] # Optional +"Homepage" = "https://www.pyopensci.org" +"Bug Reports" = "https://github.com/pyopensci/pyospackage/issues" +#"Funding" = "" +"Source" = "https://github.com/pyopensci/pyospackage" + + +``` + + +## Example `pyproject.toml` files + +Below are some examples of `pyproject.toml` files from various packages in the scientific and pyOpenSci ecosystem. +* [PyPA's fully documented example pyproject.toml file](https://github.com/pypa/sampleproject/blob/main/pyproject.toml) +* [taxpasta has a nicely organized pyproject.toml file and is a pyOpenSci approved package](https://github.com/taxprofiler/taxpasta/blob/f9f6eea2ae7dd08bb60a53dd49ad77e4cf143573/pyproject.toml) + + +## Wrap up + + +At this point you've created: + +* A [README.md](add-readme) file for your package +* A [CODE_OF_CONDUCT.md](add-coc) file to support your user community +* And a [LICENSE](add-license-coc) file which provides legal boundaries around how people can and can't use your software + +You also learned [how to publish your package to (test)PyPI](publish-pypi). + +## Publish a new version of your package to PyPI + +You are now ready to publish a new version of your Python package to (test) PyPI. When you do this you will see that the landing page for your package now contains a lot more information. + +Try to republish now. + +First, update the version of your package in your pyproject toml file. Below version is updated from +`0.1.0` to `0.1.1`. + +```TOML +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "pyospackage" +version = "0.1.1" +``` + +Now use hatch to publish the new version of your package to test.PyPI.org. + +```bash +> hatch publish -r test + +``` + +### Next (optional) step - publishing to conda-forge + +You now have all of the skills that you need to publish +your package to PyPI. + +If you also want to publish your package on conda-forge (which is a channel within the conda ecosystem), you will learn how to do that in the next lesson. + + +:::{todo} +Really good resources frm jeremiah https://daniel.feldroy.com/posts/2023-08-pypi-project-urls-cheatsheet useful (and the linked links-demo even more so) +:::