Tutorial¶
In this section, we’re going to build together a small script showcasing PyPIToken and its everyday use.
Our objective is to create a small python script that will read a user-wide PyPI token, a created a new token derived from it, restricted to a single project.
Along the way, we’ll create an empty project and upload it on Test PyPI.
Prerequisites & installation¶
You will need a virtual environment (or virtualenv
). If you’re not use to creating
one, please have a look a the virtualenv Python Packaging official documentation.
Once your virtualenv is created, let’s install pypitoken
(venv) $ pip install pypitoken build twine
Note
How to read code examples as above? The $
tells you that you are in a shell
(a bash shell, not a Python shell), the (venv)
prefix tells you that your
virtualenv
should be activated, probably with the source .../bin/activate
line that the virtualenv
tutorial above covered. So you can type or copy-paste
starting from pip
.
A bit of boilerplate¶
We’ll do this in a single file. Let’s create a file named tutorial.py
and type
the following:
import sys
def add_restriction(raw_token: str, project: str) -> str:
# TODO
return raw_token
def main():
raw_token = sys.stdin.read().strip()
try:
project = sys.argv[1]
except IndexError:
sys.exit(f"Usage: {sys.argv[0]} {{project_name}} < token")
print(add_restriction(raw_token=raw_token, project=project))
if __name__ == "__main__":
main()
So we have our main function, that reads tokens from standard input and command-line, adds a restriction (not actually implemented yet) and then print it back.
Let’s try the boilerplate on a fake token:
(venv) $ echo "hello" | python tutorial.py myproject
hello
(We’re writing hello
to the standard output and piping that to our script, which
prints back hello
.)
Preparing our token¶
We’re going to use your Test PyPI account, so you can start by creating one if you don’t have one already.
Create a user-wide token from your account page, and copy it.
We’ll save the password to an environment variable, so we can get it back later:
(venv) $ export TOKEN=< your token starting with pypi- >
In you shell, export the following environment variables, this will make twine
able
to use your token:
(venv) $ export TWINE_REPOSITORY=testpypi
(venv) $ export TWINE_USERNAME=__token__
(venv) $ export TWINE_PASSWORD=$TOKEN
Warning
Don’t put spaces around the equal sign. Bash is touchy with spaces.
Creating a project to upload¶
Our project name will be test-<your-username>-<today as yyyy-mm-dd>
. For readability
sake, we’ll be using a specific username and date throughout the documentation, but
remember to always swap with your own project name and date.
We’ll create an empty project just for the sake of testing the upload procedure.
In a file named setup.py
, type the following:
from setuptools import setup
setup(name="test-ewjoachim-2021-03-07", version="0.0.0")
Now let’s upload the package to Test PyPI:
(venv) $ python -m build
(venv) $ twine upload dist/*
Your package should be uploaded at version 0.0.0. The twine upload
command output
should end with a link to your package:
View at:
https://test.pypi.org/project/test-ewjoachim-2021-03-07/0.0.0/
Add a restriction¶
Now let’s implement our add_restriction
function in tutorial.py
:
from pypitoken import Token
def add_restriction(raw_token: str, project: str) -> str:
token = Token.load(raw_token)
print("Original restrictions:", token.restrictions, file=sys.stderr)
token.restrict(project_names=[project])
print("New restrictions:", token.restrictions, file=sys.stderr)
return token.dump()
Going line by line:
token = Token.load(raw_token)
loads the token in string form into an object. An exception might be raised here, of the typepypitoken.LoaderError
.print(...)
: This is a debug step, that will show us the restrictions our token contains. When called the second time, it should contain our new restriction.token.restrict(project_names=[project])
adds new restrictions to our token. Here, we’re using theprojects
keywords which expects a list of projects, but we only have a single project to pass, so we make a list with a single object.return token.dump()
: Then we turn our modified token back int a string and return it.
Test it¶
We’re going to make a new test, calling again:
(venv) $ echo $TOKEN | python tutorial.py test-ewjoachim-2021-03-07
This time we should see our token getting a new restriction. Let’s see if it still works:
(venv) $ export TWINE_PASSWORD=$(echo $TOKEN | python tutorial.py test-ewjoachim-2021-03-07)
(venv) $ twine upload dist/*
This should have worked. Now let’s make another token that should be rejected:
(venv) $ export TWINE_PASSWORD=$(echo $TOKEN | python tutorial.py some-other-project)
(venv) $ twine upload dist/*
This time you should have received an error. There are 3 reasons why it’s logical to have an error here:
The project
some-other-project-foo-bar
does not existYou consequently don’t have upload permissions on it
Even if you had, your token is restricted to a different project.
Thankfully, the error message is explicit in that the third error is the first one that was encountered. Of course, if you want to create a new package on Test PyPI, so that you can eleminate the two other causes, feel free, but this tutorial doesn’t cover this.
Looking back¶
Pfew! Let’s take a moment to summarize what we’ve done:
Created a project on Test PyPI
Had Test PyPI generate a token for us. The token can upload releases for any of our projects on Test PyPI
Then we locally added a restriction on the token, so that it’s now bound to only one of our projects
And then we checked that this new token still works for that project, and refuses to upload to other projects.
Oh, and we created this as a generic command-line tool that can be used to add restrictions to real PyPI tokens too!
That was a nice journey. Time for a cup of tea, and maybe a Macaroon.
Going further¶
To continue with practical steps, head to the “How to…” section.
If you want to better understand some design decisions, head to the Discussions section.