Introduction
I recently developed and published my first Python library to PyPI. This article is not about the technical details of the library itself, but rather a record of the process from knowing nothing to releasing it.
Published library:
- fastapi-websocket-stabilizer
- PyPI: https://pypi.org/project/fastapi-websocket-stabilizer/
- GitHub: https://github.com/yuuichieguchi/fastapi-websocket-stabilizer
I hope this will be helpful for anyone who wants to create a library but doesn't know where to start.
What Motivated Me
While developing real-time communication applications with FastAPI, I struggled with WebSocket connection instability.
Specifically:
- Unexpected connection drops
- No heartbeat mechanism
- Zombie connection accumulation
- Complex reconnection handling
- Cumbersome graceful shutdown implementation
I kept writing similar code to address these issues, and when I checked GitHub Issues and Stack Overflow, I found many developers facing the same challenges.
"Then maybe there's value in publishing this as a general-purpose library."
That's when I started development.
Project Structure
The first challenge was deciding on the project structure. After research, I adopted this standard structure:
project-root/
├── src/
│ └── fastapi_websocket_stabilizer/
│ ├── __init__.py
│ ├── manager.py
│ ├── config.py
│ └── ...
├── tests/
├── README.md
├── LICENSE
└── pyproject.toml
Key points:
- Used
srclayout (to use the installed package during testing) - Package name with hyphens, module name with underscores
- Chose MIT license (for wide adoption)
pyproject.toml Configuration
Modern Python projects use pyproject.toml instead of setup.py for configuration. Here's what I used:
[build-system]
requires = ["setuptools>=70.0.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "fastapi-websocket-stabilizer"
version = "0.1.0"
description = "A production-ready WebSocket stabilization layer for FastAPI applications"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
authors = [
{name = "FastAPI WebSocket Stabilizer Contributors"}
]
keywords = [
"fastapi",
"websocket",
"connection-management",
"heartbeat",
"graceful-shutdown",
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries :: Python Modules",
]
dependencies = [
"fastapi>=0.95.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.0",
"pytest-asyncio>=0.21.0",
"pytest-cov>=4.0",
"black>=23.0",
"ruff>=0.1.0",
"mypy>=1.0",
"uvicorn>=0.20.0",
]
[project.urls]
Homepage = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer"
Repository = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer"
Documentation = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer#readme"
Issues = "https://github.com/yuuichieguchi/fastapi-websocket-stabilizer/issues"
[tool.setuptools]
packages = ["fastapi_websocket_stabilizer"]
package-dir = {"" = "src"}
[tool.black]
line-length = 100
target-version = ["py310", "py311", "py312"]
[tool.ruff]
line-length = 100
target-version = "py310"
select = ["E", "F", "W", "I"]
[tool.mypy]
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_untyped_calls = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
Key points:
-
classifiersspecify categorization on PyPI -
optional-dependenciesseparate development tools -
tool.*sections integrate linter, formatter, and test configurations
Setting Up PyPI Account
Creating an Account
- Create an account at https://pypi.org
- Verify your email address
- Enable two-factor authentication (recommended)
Generating an API Token
For security, use an API token instead of a password.
- Log in to PyPI
- Go to Account settings → API tokens
- Click "Add API token"
- Select Scope: "Entire account" (for initial publication)
- Store the generated token safely
Important: The token is only displayed once, so make sure to save it.
Configuring Authentication
Create a ~/.pypirc file:
[pypi]
username = __token__
password = pypi-AgEIcHlwaS5vcmc... (your token)
Building the Package
Installing Required Tools
pip install --upgrade build twine setuptools wheel
Building
python -m build
On success, the following files will be generated in the dist/ directory:
-
.whlfile (wheel format) -
.tar.gzfile (source distribution format)
Troubleshooting: InvalidDistribution Error
During my first build, I encountered this error:
InvalidDistribution: unrecognized or malformed field 'license-file'
Cause
This was due to version conflicts between twine and packaging libraries.
Solution
# Update related tools
pip install --upgrade packaging build setuptools wheel twine
# Clear cache
rm -rf dist build *.egg-info
# Rebuild
python -m build
This resolved the issue.
Uploading to PyPI
Testing with TestPyPI (Recommended)
Before uploading to production PyPI, I recommend testing with TestPyPI:
python -m twine upload --repository testpypi dist/*
TestPyPI: https://test.pypi.org
Uploading to Production
If everything looks good, upload to production:
python -m twine upload dist/*
After upload completes, your package will be accessible at https://pypi.org/project/your-package-name/ within a few minutes.
Verification
pip install fastapi-websocket-stabilizer
Now anyone in the world can install it with this command.
Post-Publication Maintenance
Version Management
Follow semantic versioning:
- MAJOR: Breaking changes
- MINOR: Backward-compatible feature additions
- PATCH: Backward-compatible bug fixes
Example: 0.1.0 → 0.1.1 (bug fix) → 0.2.0 (new feature) → 1.0.0 (stable)
Update Process
- Modify code
- Update version in
pyproject.toml - Update
CHANGELOG.md - Rebuild and re-upload
python -m build
python -m twine upload dist/*
Documentation
For users, I prepared:
- Usage examples and API reference in README.md
- Type hints for IDE completion
- Function descriptions via docstrings
Reflections
Technical Learnings
Through library development, I learned:
- Python packaging mechanisms
- Importance of type hints
- Maintaining test coverage
- Setting up CI/CD
For Those Creating Their First Library
If you're thinking "I want to create a library but it seems daunting," here are my recommendations:
- Start small: You don't need to build something huge from the start
- Solve real problems: Libraries that solve problems you've faced have value
- Practice with TestPyPI: You can test in a staging environment before going live
- Write good documentation: Clear usage instructions attract users
- Welcome feedback: Issues are opportunities for improvement
The technical hurdles are lower than you might think. The bigger wall is probably "the courage to publish."
Conclusion
Publishing my first library was a valuable experience, not just for technical learning but also for participating in the OSS community.
If you're thinking "I want to try making one," I encourage you to take that first step.
References:
- Python Packaging User Guide: https://packaging.python.org/
- PyPI Official Documentation: https://pypi.org/help/
- fastapi-websocket-stabilizer: https://github.com/yuuichieguchi/fastapi-websocket-stabilizer
Top comments (0)