Setting Up More Configurations
00:00 In the previous lesson, I showed you how to publish your package. In this lesson, I’ll be covering more configuration things you can do to add features to your package declaration.
00:10
I kind of glossed over dependencies in the previous lessons, so let’s take a second and go into them in a bit more detail. When you declare dependencies in your package, pip
will automatically install those dependencies when your package gets installed. There are four styles of dependency declaration.
00:27
This example shows all four. The html2text
on its own here is unpinned. pip
will install the latest version of this package, whereas feedparser
says it needs at least version 5.2.0
.
00:41
pip
will install that or anything higher. You can even get more detailed than that, specifying both upper and lower boundaries, mixing and matching greater than (>
), less than (<
), and their equal to (==
) variations. For urllib3
, it has to be greater than or equal to 1.21.1
and less than 1.27
.
01:04 This kind of declaration tends to be more common with major versions. If a library does a rewrite and removes some feature or function you’re using, you may want a boundary that stops before that change.
01:16 This kind of notation is also used in testing tools like tox where you specify a group of dependencies for a particular test suite run. The last dependency here is an interesting variation on the first case.
01:28
It says you can use any version of the TOML library, but only if Python’s version is less than 3.11
. As TOML is included in the 3.11 standard library, this prevents another package being installed when the system one could be used instead.
01:44 The dependencies you’ve declared so far have just been the default ones. There are also ways of declaring other dependency targets. This is typically used to specify tools needed in dev or testing, but not by the main users.
01:59
The project.optional-dependencies
section of pyproject
lets you declare labels similar to the dependencies list, but named. When you install, you provide the optional tag in square brackets ([]
), and you get both the default dependencies and the tag ones.
02:15
I’m a big fan of the TUI debugger PuDB, and I usually install coverage
and pyflakes
in my environments, but these are just for me when I’m writing and debugging.
02:23
They aren’t needed by someone using my library. Having a dev
tag with these dependencies allows me to easily install or reinstall my preferred dev environment.
02:36 Documentation is good both for people using your library and what I like to call future you. Write enough code or let enough time elapse, and you won’t remember how something worked, and good docs and comments can make it much easier to consume your library. At minimum, if you want to be lazy, you should provide a README file.
02:54 If you don’t tell people what your library does, they’re not likely to be able to find it or use it. Both Markdown and RST are common formats for README files, and GitHub knows how to turn these into HTML for the project landing page, so your README can be double duty, both on the PyPI and on your repo home.
03:14 If you’re going to go all the way, I’d recommend looking at Sphinx, which is a documentation tool that produces PDF, e-book, HTML, and more. Sphinx-based documents host nicely on the Read the Docs documentation-hosting site. Read the Docs can hook to your repo, monitor for commits, and then build your documents and push the new versions automatically. Real Python has a course on this.
03:37 Links will be included in the Further Investigation section of the Summary lesson. If you’ve added content in other places such, as a repo or Read the Docs, you can add the URLs to the project’s metadata.
03:50
This is done in a project.urls
section. If you set these, the links will show on your pypackage
landing page, telling users where they can find more info.
04:03
The pyproject
file in the example uses a README Markdown file. Alternatively, you can give an RST file. You can also configure a dictionary in TOML instead.
04:14 The dictionary specifies the file and content type of the file, making it easier for some tools to know what your stuff is.
04:22
If you happen to have used the long_description
attribute in the setup
world, note that it doesn’t exist in the pyproject
world anymore.
04:29
You use the readme
attribute instead.
04:34
You’re writing tests, right? Best practice from a package structure point of view is to have a tests/
folder at the same level as the src/
folder.
04:44
I already mentioned using tagged dependencies, and test-specific libraries like pytest
or mocking tools are good uses of these kinds of tags.
04:53
Many of the testing tools out there are becoming more and more pyproject.toml
aware as well, so the config for the testing tool ends up in the same place as your package info.
05:05 There are different ways of specifying a version number, and there are pros and cons to each style. The two most popular are semantic versioning. That’s using major, minor, and patch numbers separated by dots, which is my preference, and calendar versioning that makes the version based on a date. There are a few variations on this one as well.
05:25
That PuDB library I mentioned earlier uses a four-digit year and a month code, while pip
itself uses a two-digit year, month, and sometimes a patch number. Version numbers can be a bit messy.
05:36
They often need to be tracked in multiple places. It’s fairly common practice to include your version number programmatically as the __version__
variable in your package’s __init__
file.
05:49
In the pyproject
example you saw earlier, the version was hard-coded. There are a couple alternatives to doing that technique as well. The first that I’m going to show you is here in this snippet.
06:00
It uses the dynamic
attribute of the project
section. This attribute indicates that certain values are generated on the fly by the build system. How to calculate the values is in a different section, which is build system–dependent. For setuptools
, the tool.setuptools.dynamic
section can indicate things to auto-generate.
06:22
By pairing the dynamic
keyword and this version info shown here, you can have your pyproject
file, get the version info from the package itself.
06:30
This requires an extra step that wasn’t needed in setup.config
because setup.config
only worked with setuptools
. The two-step mechanism here is separating the metadata from the build-system configuration. The price you pay for flexibility is complexity.
06:49
Another way of handling version info—in fact, a far more comprehensive way—is using the bumpver
third-party library. bumpver
does template replacement, so you can have your version number show up in a bunch of places, and bumver
will do the replacement in each place.
07:05
After you’ve pip
installed bumpver
, you use the init
command to have it generate the appropriate sections in your pyproject.toml
file.
07:15
This is what init
creates. In the file_patterns
section, bumpver
is defining all those places to look for a version number and what it looks like in each place. The sample here looks for one in the pyproject.toml
file, and another in the __init__
file.
07:31
The tool.bumpver
section is configuration information for bumpver
itself, telling it how to behave when you bump a version. The pattern of tool.<tool_name>
is common in pyproject.toml
, so if you’re doing something like tox, the config for that goes in tool.tox
.
07:50
Once you’ve got bumpver
configured, you use the update
command to bump your version. You have to specify to update
which part of the version is changing: --major
, --minor
, or --patch
.
08:02
The command shows you the old and new versions and then updates those files that it was configured to manage. bumpver
even integrates with git
, so you can automatically commit, tag, and push when you bump, if you want.
08:17
Something else I glossed over quickly was the contents of the reader
module. Inside of it, there was a TOML file used to configure the reader itself called config.toml
.
08:27
By default, only your Python code will be included in a package. Without extra configuration, that config.toml
file would get ignored.
08:37
To include other files, you need to create a MANIFEST.in
file in your project’s home directory. Inside this file, you list the additional files you want to include.
08:48
This is the content of MANIFEST.in
for reader
. It includes all the TOML files from the reader
module. There’s only one. Doing this allows you to ship things besides your Python code inside of your package.
09:05
In the old days, if you wanted a command that didn’t explicitly require the users to type python
and then the script name, you’d have to have a little shell thing in your package, and that meant more stuff in your manifest and complications across platforms. Now instead, you can declare in your pyproject.toml
file that you want a script.
09:24
This declaration creates a command named realpython
, and inside that command, it points at your module entry point. One of the libraries I maintain is called purdy
.
09:35 Purdy is a code and terminal display tool. You’ve actually seen me using it in this course. Purdy has several different ways it can be executed, and it can output to the terminal or to an RTF document, and as such, comes with a few different programs.
09:50
Several of the programs are really just shortcuts to the same function calls, but with different parameters. By using the scripts
feature, I can declare these in the package and then point them at different wrapper functions, and the package builder takes care of the rest. If you’ve done this, once your package is installed, the scripts are created for you and put inside of the virtual environment’s bin
directory. In the example above, I could now use realpython
from the command line as my reader.
10:18
No more need of python -m reader
.
10:23 Licensing is a full topic in itself. The very, very short, overly simplified version is if you don’t provide a license, the default under copyright law is full restrictions to the owner.
10:35 If I publish something, it’s mine, even if I put it up on the Web. If I want you to be able to change and remix it, I need to provide a license.
10:43
The license
file is the standard way of doing this in your projects. The most common license in the Python world is the MIT License, which is generally permissive, meaning the code is out there in the wild for anyone to do anything with it.
10:57 Licenses are legal documents, so you really shouldn’t create your own. GitHub actually prompts you now when you create a new repo as to what license you’d like to use with your project.
11:08 If you want more info on how to choose a license, see the Choose a License website.
11:16
If you happen to be on a Unix-based box, this is a script I have in my projects to tie a bunch of things together. First, it grabs the __version__
value out of the __init__
file and uses the cut
command to get just the version number.
11:30
Then I tag the repo with that number, and if all goes well, I remove any build
or dist
directories, then run the build
command.
11:38
Once build
is done, I run twine check
, and although you could do the upload here, sometimes things go wrong, so I’ve opted for the easy way out and just remind myself exactly what I have to type to do the upload. When I’m ready for a new version to be published, I run this script—it’s usually called clean_build
—and then if all goes well, I type in that last twine
thing, and bingo: PyPI has my latest stuff.
12:07
Mostly I’ve been showing you things focused on setuptools
. There are alternatives out there, though. In the next lesson, I’ll briefly introduce you to Flit and Poetry.
Become a Member to join the conversation.