Metadata-Version: 2.4
Name: pyloose
Version: 0.1.5
Summary: Build Python wheels and publish releases to GitLab, GitHub or PyPI
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: build<2,>=1.2
Requires-Dist: twine<8,>=6
Requires-Dist: packaging>=24

# pyloose

Build Python wheels and publish them to GitLab's Package Registry, GitHub
Releases or PyPI. Each target repository keeps its own identity, version series,
Git history and credentials. pyloose is a development/release tool.

## Use

List pyloose and its GitLab package index in your release environment's
requirements file. For local development, see the bootstrap instructions below.

From the target repository root:

```bash
pyloose release local
pyloose release gitlab
pyloose release github
pyloose release pypi
```

Or select the root explicitly from another directory:

```bash
pyloose release gitlab --project /absolute/path/to/repository
```

`python -m pyloose` provides the same commands. `pyloose --version` displays
the installed tool version. Run `pyloose release gitlab --help` for credential options.

To save the prepared files that belong to each GitHub release, list them in the
project's `pyproject.toml`:

```toml
[tool.pyloose.github]
assets = ["dist/examples.zip", "dist/release-guide.pdf"]
```

Then run `pyloose release github`. Pyloose prints the configuration file it uses.
Alternatively, supply the files with repeated `--asset` options:

```bash
pyloose release github --asset dist/examples.zip --asset dist/release-guide.pdf
```

Each file must exist when asset validation runs. Relative paths start at the selected project root,
including with `--project`; absolute paths also work. Supplying assets in both
places is an error; the lists are never merged or overridden. With neither source
of assets, only the wheel is published. Extra files go only to GitHub, including
when using `--also-pypi`.
Pyloose preserves their filenames and does not automatically version them. An
optional `prerelease_run` list can prepare assets before validation, using nested
argument arrays for commands or scripts. Commands run in order and stop on the
first failure; see the GitHub guide for examples.
See [GitHub attachments and recovery](docs/github.md) for validation and recovery details.

Wheel output defaults to `dist/` under the selected project root. Set a project
default in `pyproject.toml`:

```toml
[tool.pyloose]
outdir = "release-files"
```

`--outdir PATH` overrides that setting for any release target:

```bash
pyloose release local --outdir release-files
pyloose release github --outdir release-files
```

Relative output paths use the selected project root; absolute paths are supported.
Keep an output directory inside the repository ignored by Git. Pyloose creates
the directory as needed and preserves existing files; it never overwrites a wheel.

## Project requirements

The selected directory must be the Git repository root itself. pyloose rejects
a subdirectory and shows the correct root; it never silently switches repositories.
The only required configuration file is `pyproject.toml`. It must contain:

- A valid `[project].name`, with `version` declared in `dynamic` rather than a fixed version.
- The `setuptools.build_meta` backend and `setuptools-scm` in its build requirements.
- A `[tool.setuptools_scm]` table.
- A `[tool.pyloose.version]` table with exactly `major` and `minor` as non-negative integers.

Put pyloose in `install/requirements.txt` for the development environment.
Keep it out of `[project].dependencies` so the published package does not
require the release tool at runtime.

Here is a complete configuration for a project with its package under `src/`.
It is also available as [examples/pyproject.toml](examples/pyproject.toml):

```toml
[build-system]
requires = ["setuptools>=77", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"

[project]
name = "example_package"
dynamic = ["version"]
description = "Example package released with pyloose"
readme = "README.md"
requires-python = ">=3.11"
dependencies = []

[tool.setuptools.packages.find]
where = ["src"]

[tool.setuptools_scm]

[tool.pyloose.version]
major = 0
minor = 1
```

For repositories using the included [installer](install/README.md), run from
outside an active environment:

```bash
./install/install.py
source .venv/bin/activate
pyloose release local
```

For example, `install/requirements.txt` can contain:

```text
--extra-index-url https://gitlab-int.switch.ch/api/v4/projects/2783/packages/pypi/simple/
pyloose
-e .
```

The installer creates `.venv`, upgrades pip and installs that requirements file.
The package index must allow anonymous downloads; publishing credentials remain
separate. Pin `pyloose` to a version here if you need repeatable tool versions.
Update an existing environment with `python -m pip install -r install/requirements.txt`.

The example's `0.1` series starts with `0.1.0`; existing Git tags determine the next patch. Set `major` and
`minor` intentionally in a normal commit. pyloose never rewrites these settings.
All release settings are in `pyproject.toml`; a `packaging/` directory is unnecessary.

## Building and publishing

Builds and publishing both require a clean Git checkout. Staged, unstaged and
untracked changes stop a build before any build tools run or a wheel is created;
ignored files do not block it. Commit or set aside changes first.

`pyloose release local` builds and validates a wheel without tagging or publishing.
It uses the version inferred by setuptools-scm. Add `--package-version 0.1.0` to
build a specific version locally. Wheels go into the selected output
directory (`dist/` by default); an existing wheel is never overwritten. Keep that directory, `build/`
and `*.egg-info/` in the target's `.gitignore`.

Publishing requires a clean checkout with a commit and one `origin` push URL.
It selects the next patch from local and remote tags, builds and validates the
wheel, then pushes only the new version tag. GitLab also uploads the wheel and
creates a Release with commit-based notes; GitHub attaches the wheel to a Release.
PyPI uploads the wheel to the public package index. Add `--also-pypi` to a GitLab
or GitHub release to upload its exact wheel to PyPI without a second build or tag:

```bash
pyloose release gitlab --also-pypi
```

GitLab credentials still come from the target package's `~/.pypirc` section and
the system keyring. pyloose does not store tokens in repositories or require
the target package to depend on it at runtime.

See [GitLab setup and recovery](docs/gitlab.md),
[GitHub setup and recovery](docs/github.md) or
[PyPI setup, self-publishing and recovery](docs/pypi.md).

## Development and first local wheel

With no virtual environment active, run from the pyloose repository root:

```bash
./install/install.py
source .venv/bin/activate
pyloose release local --package-version 0.1.0
```

The generic installer creates `.venv` and installs this project and its
dependencies. It refuses an active environment or an existing `.venv`.
The first wheel can be installed in another repository's active environment:

```bash
python -m pip install --find-links /absolute/path/to/pyloose/dist pyloose==0.1.0
```

Once the tool is published, install the pinned version from your configured
package index instead. pyloose can publish itself with `pyloose release gitlab` or
`pyloose release pypi` after its changes are committed, an origin is configured,
and credentials are set up.
Move any local wheel for that same version aside before publishing.

## Tests

```bash
.venv/bin/python -B -m unittest discover -s tests -v
.venv/bin/python -B -m unittest discover -s install -p 'test_install.py'
```

Publisher tests use temporary repositories and mocked services. They never
publish releases, upload wheels, or push tags to a real remote.
