# Python with pip, uv and Poetry

> Install Python packages from Telesphorepo with pip, uv or Poetry, and what is different when you publish wheels and sdists.

Source: https://docs.telesphorepo.com/python/

This page sets up pip, uv or Poetry to install packages from Telesphorepo. To publish a package, see [Publish from a project](/publish-from-a-project/), under Publish your packages; what is different for Python is under [Publish to this repository](#publish-to-this-repository).

An admin first makes a repository with the format **Python (PyPI)** (see [Make a repository](/repositories-and-access/#make-a-repository)). The examples use the server `repo.example.com` and a repository named `python`, so the index is this:

```text
https://repo.example.com/pypi/python/simple/
```

Put your own values in the blocks below. The **Use this repository** tab of the repository shows the same blocks with your values.

## Install packages

1. Make a read token.

   #### Anyone can download

   Skip this step. Anyone can download.

   #### Only signed-in people

   **Settings** › **Tokens** › **New read token**. Type a name, then click **Create token**.

2. Point the tool at the repository.

   #### pip, anyone can download

   ```ini title="~/.config/pip/pip.conf"
   [global]
   index-url = https://repo.example.com/pypi/python/simple/
   ```

   On Windows, the file is `%APPDATA%\pip\pip.ini`.

   #### pip, only signed-in people

   ```ini title="~/.config/pip/pip.conf"
   [global]
   index-url = https://YOUR_USERNAME:YOUR_READ_TOKEN@repo.example.com/pypi/python/simple/
   ```

   pip reads the username and the token from the address. On Windows, the file is `%APPDATA%\pip\pip.ini`. The file holds your token, so keep it in your home folder.

   #### uv

   ```toml title="pyproject.toml"
   [[tool.uv.index]]
   name = "python"
   url = "https://repo.example.com/pypi/python/simple/"
   default = true
   ```

   #### uv, only signed-in people

   Then give uv your username and the token. Paste the token at the hidden prompt.

   ```sh
   printf 'Read token: '; read -rs READ_TOKEN; echo
   export UV_INDEX_PYTHON_USERNAME=YOUR_USERNAME
   export UV_INDEX_PYTHON_PASSWORD="$READ_TOKEN"
   ```

   The variables name the index in capitals, with `_` for every character other than a letter or a digit. In CI, set them as secrets.

   #### Poetry, anyone can download

   ```sh
   poetry source add --priority=primary python https://repo.example.com/pypi/python/simple/
   ```

   #### Poetry, only signed-in people

   Paste the token at the hidden prompt.

   ```sh
   poetry source add --priority=primary python https://repo.example.com/pypi/python/simple/
   printf 'Read token: '; read -rs READ_TOKEN; echo
   poetry config http-basic.python YOUR_USERNAME "$READ_TOKEN"
   ```

3. Install a package.

   #### pip

   ```sh
   pip install "acme-billing==1.2.0"
   ```

   #### uv

   ```sh
   uv add "acme-billing==1.2.0"
   ```

   #### Poetry

   ```sh
   poetry add "acme-billing==1.2.0"
   ```

Done when the tool installs the package with no error.

## Your own packages

A hosted repository holds your own packages only. pip then cannot install their dependencies from PyPI through it. To install both through one index, ask your server admin for a group of your repository and a proxy of PyPI. Then point the tool at the group.

## Public packages too

A group asks its members in order. The first member that has a project name owns it, and the group serves every file of that project from that member only. So a package on PyPI with the name of your private package cannot take its place, even with a higher version. Your admin puts your hosted repository before the proxy.

## Publish to this repository

Publish as in [Publish from a project](/publish-from-a-project/): run the setup line in the folder that holds `pyproject.toml` or `setup.py`, commit `telesphorepo.toml`, then run `telesphorepo publish`.

Done when the CLI prints `Uploaded` for each file, for example `Uploaded acme_billing-1.2.0-py3-none-any.whl (acme-billing 1.2.0) to python`.

The CLI builds the project into a temporary folder, then uploads every wheel and sdist of the build:

| Project                             | The CLI builds with                                              |
| ----------------------------------- | ---------------------------------------------------------------- |
| `[tool.poetry]` in `pyproject.toml` | `poetry build`                                                   |
| a `uv.lock`                         | `uv build`                                                       |
| anything else                       | `python -m build`. Install it with `python -m pip install build` |

To upload files that you built, name them: `telesphorepo publish dist/*.whl`. The CLI then builds nothing.

A file that is on the server already, with other bytes, answers `409 File already exists`. Raise the version.

Publish with twine, uv or Poetry

These tools upload with a publish token. Make one in **Settings** › **Tokens** › **New publish token**: choose the repository, then confirm with your second factor. The token works for 15 minutes. The username is `__token__`, and the upload address is `https://repo.example.com/pypi/python/`.

twine asks for the token at its password prompt:

```sh
twine upload --repository-url https://repo.example.com/pypi/python/ -u __token__ dist/*
```

uv reads the token from the environment:

```sh
printf 'Publish token: '; read -rs UV_PUBLISH_TOKEN; echo
export UV_PUBLISH_TOKEN
uv publish --publish-url https://repo.example.com/pypi/python/
```

Poetry reads it from the environment too:

```sh
poetry config repositories.telesphorepo https://repo.example.com/pypi/python/
printf 'Publish token: '; read -rs POETRY_HTTP_BASIC_TELESPHOREPO_PASSWORD; echo
export POETRY_HTTP_BASIC_TELESPHOREPO_USERNAME=__token__ POETRY_HTTP_BASIC_TELESPHOREPO_PASSWORD
poetry publish --build -r telesphorepo
```

A password or a read token cannot upload.

### The CI file for Python

[Publish from CI](/publish-from-ci/) has the steps. When you choose **GitHub Actions** at setup, the CLI writes the file for the build tool of the project:

### GitHub Actions with pip

```yaml title=".github/workflows/publish.yml"
on:
  push:
    branches: ["main"]

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v6
        with:
          python-version: 3.x
      - run: python -m pip install build
      - run: curl -fsSL https://repo.example.com/install.sh | sh
      - run: telesphorepo publish
```

### GitHub Actions with uv

```yaml title=".github/workflows/publish.yml"
on:
  push:
    branches: ["main"]

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: astral-sh/setup-uv@v7
      - run: curl -fsSL https://repo.example.com/install.sh | sh
      - run: telesphorepo publish
```

### GitHub Actions with Poetry

```yaml title=".github/workflows/publish.yml"
on:
  push:
    branches: ["main"]

permissions:
  id-token: write
  contents: read

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v6
        with:
          python-version: 3.x
      - run: pipx install poetry
      - run: curl -fsSL https://repo.example.com/install.sh | sh
      - run: telesphorepo publish
```

For Jenkins, see [Publish from CI](/publish-from-ci/).

## If something goes wrong

pip ignores an `http://` index

pip uses an `http://` index only on `localhost`, or when its host is trusted. Add `trusted-host = repo.example.com` under `[global]` in `pip.conf`. For real use, serve the server over HTTPS. See [Serve over HTTPS](/run-the-server/#serve-over-https).

pip cannot find a dependency, such as `requests`

The repository is hosted, and holds your own packages only. Ask your server admin for a group with a proxy of PyPI.

401 when you install

The repository is for signed-in people only. Check the username and the read token. For uv, the variables must name the index in capitals: `UV_INDEX_PYTHON_USERNAME` for the index `python`.

A group serves your private package, not the public one with the same name

The first member that has a project owns it. Rename your project, or ask your admin to change the order of the members.

`409 File already exists`

A file with that name is on the server with other bytes. Raise the version. The same bytes again are fine.

400 when you upload

The name and the version in the file name must match the metadata inside the file. Build the file again with your build tool.

The CLI says that poetry, uv or python is not on the PATH

The CLI builds with the tool of the project. Install it, or build yourself and run `telesphorepo publish dist/*`.

twine prints only the status line

Add `--verbose`. twine then prints the reason that the server gives.
