Publishing a Pure-Python CLI with Conda - 9/9
Deciding when a conda package adds value, writing a noarch recipe, testing it, and publishing it without unnecessary platform conversion.
PyPI is the natural distribution channel for a pure-Python CLI. A conda package adds value when the intended users already manage complete environments through conda or when a project depends on software outside the Python package ecosystem.
tinyHTTPie does not require compiled libraries, so conda is not technically necessary. It is still a useful final step because it shows how the same application metadata is translated into another package ecosystem—and where duplication can introduce inconsistencies.
Series
- From a Script to a Command-Line Tool
- When
sys.argvStops Scaling - Designing a CLI with
argparse - Why a Bash Installer Becomes a Liability
- Packaging a Python CLI with Console Scripts
- Testing a Python CLI and Automating CI
- Documentation as Part of the Product
- Building a Reliable PyPI Release Pipeline
- Publishing a Pure-Python CLI with Conda
Decide whether the package is platform-specific
The application contains only Python code and uses Requests, which is also platform-independent at this level. Building separate Linux, macOS, Windows, and Python-version archives would duplicate the same files.
Conda represents this with noarch: python. One package can be installed across supported platforms, and conda creates the platform-appropriate command entry point during installation.
Write a focused recipe
Place the recipe in conda.recipe/meta.yaml next to the Python project:
{% set version = "0.1.0" %}
package:
name: tihttp
version: {{ version }}
source:
path: ..
build:
number: 0
noarch: python
script: {{ PYTHON }} -m pip install . -vv --no-deps --no-build-isolation
entry_points:
- tihttp = tihttp.cli:run
requirements:
host:
- python >=3.6
- pip
- setuptools
- wheel
run:
- python >=3.6
- requests >=2.21
test:
imports:
- tihttp
commands:
- tihttp --help
about:
summary: A small command-line HTTP client
license: MIT
license_file: LICENSE
The dependency sections describe different environments:
hostcontains tools and libraries needed to build or install the package for its target environment.runcontains dependencies that must be present when a user executestihttp.testverifies both the importable package and the generated command.
For a released recipe, I would normally build from the published source archive and verify its sha256 digest rather than use a local path. The local path keeps this lifecycle example focused on the recipe itself.
Build and inspect the output
Build the recipe and ask conda-build for the resulting artifact path:
$ conda build conda.recipe
$ conda build conda.recipe --output
/path/to/conda-bld/noarch/tihttp-0.1.0-py_0.tar.bz2
The exact output directory depends on the conda installation, which is why scripts should ask conda build --output for the artifact path instead of constructing it themselves.
Because the recipe is noarch, there is no platform-conversion loop. Converting packages is not a substitute for building native binaries on their target platforms; for a pure-Python project, one architecture-independent package is the simpler representation.
Install the local artifact into a clean environment before publishing:
$ conda create --name tihttp-release \
--channel local tihttp=0.1.0
$ conda run --name tihttp-release tihttp --help
Choose a channel and ownership model
Anaconda.org supports personal and organizational channels. With the Anaconda client authenticated, the rendered package path can be uploaded explicitly:
$ package_path="$(conda build conda.recipe --output)"
$ anaconda upload "$package_path"
For a broadly useful open-source package, conda-forge offers a different ownership model. A feedstock repository stores the recipe, automated infrastructure builds it, and community review helps maintain dependency and platform metadata. That adds process, so it should follow real user demand rather than being created only to increase the number of badges on a README.
Whichever channel we choose, metadata must remain consistent with setup.py: name, version, license, Python requirement, runtime dependencies, and entry point. Duplicated metadata is a maintenance boundary and deserves a release check.
The completed lifecycle
The series began with one hard-coded request. The same small program now has:
- a defined command-line interface;
- explicit failure behavior and exit codes;
- standard package metadata and a generated command;
- deterministic tests and a support matrix;
- user and maintainer documentation;
- traceable PyPI release artifacts;
- an optional conda distribution for environment-oriented users.
The code did not need to become large. Most of the evolution happened at its boundaries. That is the central lesson: turning a script into a tool means making its contracts explicit enough that people and other tools can rely on it.