From Dev to Deployment: How I Published 'gh-echo v1.0.0' to PyPI
An End-to-End Guide on Building, Testing, and Publishing a Python CLI Tool with Poetry
Introduction
Hello Everyone! I’m excited to announce that GitHub Echo Version 1.0.0 is finally out! 🎉 This release comes packed with features and improvements compared to the previous version available on PyPI.
Here’s what’s new:
Improved Config File Support: Streamlined usage for better customization.
Enhanced Error Handling: Covering more edge cases to ensure a seamless experience.
Better Documentation: Comprehensive examples and detailed guides to get you started quickly.
To make this release as robust as possible, I had to:
Test all aspects of the application extensively.
Add error handling for numerous use cases.
Write additional tests to ensure reliability.
Enhance the documentation with clear examples.
I’ve built this tool using Poetry for dependency management and publishing, and it's available on PyPI. In this blog, I’ll walk you through my release process, the tools I used, the commands I ran, and the actions I took to publish GitHub Echo 1.0.0 successfully.
The pyproject.toml
file
If you're using Poetry for dependency management and packaging like I am, you already have a pyproject.toml
file. This file is how Poetry tracks project configurations, dependencies, and metadata. However, to package and publish your project successfully, additional setup is required in this file.
Below is a complete example of my pyproject.toml
file for GitHub Echo, and I’ll walk you through what each line does and why it’s important.
[tool.poetry]
name = "gh-echo"
version = "1.0.0"
description = "A command-line tool built to obtain in-depth, actionable information about GitHub repositories."
authors = ["Aryan Khurana <aryankhurana1511@gmail.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://pypi.org/project/gh-echo/o"
repository = "https://github.com/AryanK1511/github-echo"
documentation = "https://github.com/AryanK1511/github-echo/blob/main/README.md"
keywords = ["GitHub", "GenAI", "Python3", "CLI"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent"
]
packages = [{include = "application"}]
include = ["_main.py", "_config.py", "_constants.py", "pyproject.toml"]
[tool.poetry.scripts]
lint = "_scripts:lint"
format = "_scripts:format_code"
lint-and-format = "_scripts:lint_and_format"
run-tests = "_scripts:run_tests"
run-tests-on-files = "_scripts:run_tests_on_files"
run-tests-on-classes = "_scripts:run_tests_on_classes"
run-coverage = "_scripts:run_coverage"
run-coverage-report = "_scripts:run_coverage_report"
run-coverage-html = "_scripts:run_coverage_html"
watch-tests = "_scripts:watch_tests"
watch-tests-coverage = "_scripts:watch_tests_with_coverage"
gh-echo = "_main:app"
[tool.poetry.dependencies]
python = "^3.9"
typer = "^0.12.5"
google-generativeai = "^0.8.0"
python-dotenv = "^1.0.1"
httpx = "^0.27.2"
pytest = "^8.3.3"
single-source = "^0.4.0"
groq = "^0.11.0"
toml = "^0.10.2"
pre-commit = "^4.0.1"
coverage = "^7.6.4"
pytest-watch = "^4.2.0"
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.4"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
The [tool.poetry]
section contains metadata about my project that is crucial for publishing and distributing it on platforms like PyPI.
[tool.poetry]
name = "gh-echo"
version = "1.0.0"
description = "A command-line tool built to obtain in-depth, actionable information about GitHub repositories."
authors = ["Aryan Khurana <aryankhurana1511@gmail.com>"]
license = "MIT"
readme = "README.md"
homepage = "https://pypi.org/project/gh-echo/"
repository = "https://github.com/AryanK1511/github-echo"
documentation = "https://github.com/AryanK1511/github-echo/blob/main/README.md"
keywords = ["GitHub", "GenAI", "Python3", "CLI"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent"
]
name
: The package name as it will appear on PyPI.version
: Version number following Semantic Versioning.description
: A brief description of what the package does.authors
: List of authors in the formatName <email>
for credit and contact.license
: Indicates the licensing model; in this case, it's MIT.readme
: The file containing the long description of your project (usually Markdown).homepage
: Link to the project’s PyPI page.repository
: URL of the project’s source code repository.documentation
: Link to the documentation, often theREADME.md
on GitHub.keywords
: Tags to make your project searchable.classifiers
: PyPI Classifiers to categorize the package.
Packages and Include
packages = [{include = "application"}]
include = ["_main.py", "_config.py", "_constants.py", "pyproject.toml"]
packages
: Specifies the directory to include as part of the package (e.g., theapplication
folder).include
: Ensures additional files outside the package (like config files) are included when the package is distributed.
[tool.poetry.scripts]
[tool.poetry.scripts]
gh-echo = "_main:app"
This section creates CLI commands for my project.
gh-echo
: The command users will type in the terminal to run my application._main:app
: Points to the entry point of my application, whereapp
is defined with Typer.
Dependencies
These are the dependencies and the development dependencies that the project needs to run.
[tool.poetry.dependencies]
python = "^3.9"
typer = "^0.12.5"
google-generativeai = "^0.8.0"
python-dotenv = "^1.0.1"
httpx = "^0.27.2"
pytest = "^8.3.3"
single-source = "^0.4.0"
groq = "^0.11.0"
toml = "^0.10.2"
pre-commit = "^4.0.1"
coverage = "^7.6.4"
pytest-watch = "^4.2.0"
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.4"
[build-system]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
requires
: Specifies the required build backend (Poetry).build-backend
: Points to the Poetry core system for handling builds.
Why Is This File Important?
This file is critical for:
Dependency management: Poetry ensures all dependencies are properly tracked.
Publishing: The metadata is required for uploading your package to PyPI.
Automation: The
scripts
andinclude
sections allow for custom workflows.
Building and Publishing
In this section, we’ll walk through the entire process of building and publishing a Python package to both TestPyPI and PyPI using Poetry. This will allow others to pip install gh-echo
and start using your tool effortlessly. Before proceeding, ensure you’ve configured your pyproject.toml
file as detailed in the previous section.
What Happens When You Publish a Package?
When you run pip install
, Python fetches the package from the PyPI repository. To make your package available for this process, you need to upload it to PyPI.
However, once a specific version is published on PyPI, you cannot overwrite it. Therefore, we first use TestPyPI, a testing environment, to ensure everything works as expected before publishing to the main repository.
Here’s a complete step-by-step guide to achieve this.
1. Create an Account on TestPyPI and PyPI
Visit TestPyPI and PyPI to create accounts if you don’t already have them.
Once registered, generate an API token on each platform:
Save these tokens securely, as they will be used for authentication.
2. Configure Poetry to Use the Repositories
You need to tell Poetry where to publish your package. Add TestPyPI and PyPI as repositories in your Poetry configuration:
poetry config repositories.testpypi https://test.pypi.org/legacy/
poetry config repositories.pypi https://upload.pypi.org/legacy/
Next, set up authentication credentials for both repositories using your API tokens:
poetry config pypi-token.testpypi <your_testpypi_token>
poetry config pypi-token.pypi <your_pypi_token>
These commands save your credentials securely in Poetry’s configuration file.
📖 Learn more about adding repositories here.
3. Build Your Package
Once your pyproject.toml
is ready, build your package into a distributable format:
poetry build
This will create the following files in the dist/
directory:
A
.tar.gz
file: Source distribution (sdist).A
.whl
file: Built distribution (wheel).
4. Publish to TestPyPI
Before publishing to PyPI, test your package on TestPyPI:
poetry publish -r testpypi
This command uploads your package to TestPyPI. Ensure everything works as expected by installing your package from TestPyPI in a virtual environment:
pip install -i https://test.pypi.org/simple/ gh-echo
Test your package thoroughly after installation.
5. Publish to PyPI
Once satisfied with your package’s functionality on TestPyPI, publish it to the main PyPI repository:
poetry publish --no-interaction --verbose
This command uploads your package to PyPI, making it publicly available.
6. Verify and Install
After publishing, verify that your package is live on PyPI by visiting the homepage (e.g., https://pypi.org/project/gh-echo/).
To test installation from PyPI, run:
pip install gh-echo
Completing the Release Process: Versioning, Tagging, and Pushing to GitHub
After publishing the package to PyPI, the next step is to ensure your repository is up-to-date and reflects the changes you’ve made. This includes committing all changes, creating a release tag, and pushing everything to GitHub.
Here’s how you can do it step by step:
1. Commit All Changes
First, ensure all your changes, including the updates to the pyproject.toml
, README.md
, or any other files, are committed.
Run the following commands:
# Stage all changes
git add .
# Commit changes with a meaningful message
git commit -m "Prepare release 1.0.0: Update pyproject.toml, add docs, and publish to PyPI"
2. Create a Git Tag
Tags are essential for marking specific points in your repository's history as release versions. You can use a lightweight or annotated tag. Here, we’ll create an annotated tag for version 1.0.0
:
git tag -a v1.0.0 -m "Release version 1.0.0: First stable release of gh-echo"
This command:
-a
: Creates an annotated tag.-m
: Adds a message describing the tag.
3. Push Changes to GitHub
Push all your committed changes along with the newly created tag to GitHub:
# Push all commits
git push origin main
# Push the tag
git push origin v1.0.0
4. Create a Release on GitHub
To make your release official, create a release on GitHub. Here’s how I did it:
Go to the repository’s Releases page.
Click "Draft a new release".
Select the tag
v1.0.0
from the dropdown menu or type it in.Add a release title like "gh-echo v1.0.0: First Stable Release".
Provide a summary of what’s new in this version or let GitHub generate the automated change log.
Attach any relevant release artifacts if needed (e.g.,
.tar.gz
,.whl
files from thedist/
folder).Click "Publish Release".
Final Verification and Conclusion
To ensure everything is correctly set up:
Check your GitHub repository for the new tag and release.
Verify the PyPI page for
gh-echo
to confirm the latest version is live.Install your tool using:
pip install gh-echo
Now your package is ready for the world to use! 🎉
You can also check out my Release on Github and Package on PyPI.