Python

 View Only

Building a Python Package

By Kiarash Alirezaei posted 06/22/26 04:58 PM

  

Building and Managing Python Packages 

PyPI receives over 2 billion monthly downloads. This guide covers the process of building and publishing Python packages - from deciding what to package, to building, testing, and deploying to PyPI. 

Why Build a Python Package? 

Publishing your package makes your code accessible to developers worldwide and enables simple installation with pip install your-package-name . This simplifies distribution, encourages adoption of your work, and contributes to the open-source ecosystem. PyPI serves as the canonical source of Python packages, providing centralized discovery and version management. For organizations that do not want to distribute packages externally, internal PyPI repositories such as Nexus or Artifactory can be used. These private repositories allow teams to share packages within their organization while maintaining control over access and security. Installation from internal repositories simply requires adding the --index-url option: pip install --index-url https://your-internal-pypi.com/simple your-package-name. 

What Should You Package? 

Not all code should be packaged. Reusable code makes good package material - libraries (collections of reusable functions, classes, and variables), frameworks (pre-built structures for building applications), and tools (command-line utilities or scripts for specific tasks like data processing or automation).  

Avoid packaging non-reusable code such as application-specific code, internal tools, or experimental projects. Also avoid packaging low-quality or incomplete code with known security vulnerabilities, unmaintained projects, or code lacking proper documentation or testing. The key question is: "Will others find this useful and reusable?" 

Package Naming Best Practices 

Choose a descriptive, unique name that: 

  • Uses lowercase letters and hyphens (e.g., my-awesome-tool) 

  • Avoids common words that might conflict with existing packages 

  • Doesn't infringe on trademarks 

  • Is easy to remember and type 

 

Project Setup and Code Structure 

The first step is setting up your development environment. Virtual environments provide isolated spaces for package development without affecting system Python installations. They contain your work, avoid version conflicts, and enable easy cleanup. Using venv is simple:  

# Create virtual environment 
python3 -m venv projectA 

# Activate (sh/POSIX shell - z/OS default) 
. projectA/bin/activate 
 
# OR activate (bash shell) 
source projectA/bin/activate 

# Work in your isolated environment 
# ... 

# Deactivate when done 
deactivate 

Project Structure

Once your environment is ready, organize your project structure. Successful project organization should follow these principles: clear structure (standardize your repository organization), modular design (separate code into logical modules with single responsibilities), test-driven development (include tests for each module), documentation (provide clear information for users and contributors), and examples (offer use cases to help users get started quickly). 

A typical structure includes several key components: a src directory containing your package modules and source code, a tests directory for test files, and a docs directory for documentation; additionally, files such as pyproject.toml for project configuration and dependencies, requirements.txt for specifying project dependencies, README.md for project overview and usage instructions, LICENSE for open-source terms, and .gitignore to exclude unnecessary files from version control. 

Essential Project Files with Examples: 

  1. pyproject.toml (Modern Python projects)
    [build-system] 
    requires = ["setuptools>=45", "wheel"] 
    build-backend = "setuptools.build_meta" 
    
    [project] 
    name = "your-package-name" 
    version = "0.1.0" 
    description = "A brief description of your package" 
    authors = [{name = "Your Name", email = "your.email@example.com"}] 
    dependencies = [ 
        "numpy>=1.20.0", 
        "requests>=2.25.0" 
    ] 
    
    [project.optional-dependencies] 
    dev = ["pytest>=6.0", "black", "flake8"] 

  2. setup.py (Alternative/Legacy approach)
     
    from setuptools import setup, find_packages 
    
    setup( 
        name="your-package-name", 
        version="0.1.0", 
        packages=find_packages(where="src"), 
        package_dir={"": "src"}, 
        install_requires=[ 
            "numpy>=1.20.0", 
            "requests>=2.25.0", 
        ], 
        python_requires=">=3.7", 
    ) 
  3. src/your_package/init.py
    """ 
    Your Package Name 
    A brief description of what your package does. 
    """ 
    
    __version__ = "0.1.0" 
     
    # Import main classes/functions to make them available at package level 
    from .core import MainClass 
    from .utils import helper_function 
     
    __all__ = ["MainClass", "helper_function"] 

    Why init.py is required: This file marks the directory as a Python package, allowing imports like from your_package import MainClass. It also controls what gets exported when users do from your_package import *. 

  4. README.md structure
    # Project Name 
    Brief description of what your project does. 
    
    ## Installation 
    ```bash  
    pip install your-package-name  
    ``` 
    
    ## Quick Start 
    ```python  
    from your_package import MainClass 
    
    ## Example usage 
    obj = MainClass()  
    result = obj.do_something()  
    ``` 
    
    ## Documentation 
    Link to full documentation 
    
    ## Contributing 
    Guidelines for contributors 
    
    ## License 
    MIT License (or your chosen license) 
     
  5. Directory structure example 
image

Testing 

After setting up your project structure, testing becomes crucial. Testing ensures code quality and reliability, builds trust with users and community, and reduces maintenance and technical debt. Not testing leads to delayed identification of regressions, increased maintenance and debugging time, and decreased project sustainability. 

Python has two popular testing frameworks. unittest is included in the standard library since Python 2.1, provides special assertion methods and fixtures using classes and methods, and uses the naming convention test_*.py or *_test.py.

# tests/test_core.py 

import unittest from my_package.core  
import add_numbers 

class TestCore(unittest.TestCase):  

   def test_add_positive_numbers(self):    
      self.assertEqual(add_numbers(2, 3), 5) 

   def test_add_negative_numbers(self): 
      self.assertEqual(add_numbers(-1, -1), -2) 

   def setUp(self): 
      """Run before each test""" 
      pass 
 
   def tearDown(self): 
      """Run after each test""" 
      pass 

Run tests:

# Run all module tests 
python -m unittest test_module 

# Run one specific test 
python -m unittest test_module.TestAbc 

# Discover and run all tests 
python -m unittest discover -s tests -p "test_*.py" 

pytest is an open-source testing framework available on PyPI with advanced test discovery capabilities, simplified workflow, and an extensive plugin ecosystem. 

tests/test_core.py 

import pytest from my_package.core  
import add_numbers, divide_numbers 

def test_add_positive_numbers():  
   assert add_numbers(2, 3) == 5 

def test_add_negative_numbers():  
   assert add_numbers(-1, -1) == -2 

def test_add_zero(): 
    assert add_numbers(0, 0) == 0 

def test_divide_by_zero(): 
    with pytest.raises(ZeroDivisionError): 
        divide_numbers(10, 0) 

Install and Run:

# Install pytest 
pip install pytest 

# Run all package tests 
pytest 

# Run with coverage 
pip install pytest-cov  
pytest --cov=my_package 

# Run one test module 
pytest tests/test_core.py 

# Run one specific test 
pytest tests/test_core.py::test_add_positive_numbers 

More Information: https://docs.pytest.org/en/stable/

Why pytest over unittest? 

While Python's built-in unittest module is available without installation, pytest has become the industry standard for several reasons: it uses simple assert statements instead of specialized assertion methods (like self.assertEqual()), provides more detailed failure messages, offers powerful fixtures for test setup, has automatic test discovery, and includes an extensive plugin ecosystem for coverage reporting, parallel testing, and more. For beginners and experienced developers alike, pytest's cleaner syntax and better error messages make tests easier to write and debug, hence many open-source projects use this for testing as well. 

 

Packaging and Development 

With your code tested and working, the next step is packaging it for distribution. Modern Python projects use pyproject.toml as the central configuration file. This provides unified configuration and enhances reproducibility by precisely defining project dependencies for consistent builds. 

Here's a basic pyproject.toml example: 

toml 

[build-system] 
requires = ["setuptools>=61.0"] 
build-backend = "setuptools.build_meta" 

[project] 
name = "my-package" 
version = "0.1.0" 
description = "A simple Python package" 
authors = [{name = "Your Name", email = "your.email@example.com"}] 
dependencies = [ 
    "requests>=2.28.0", 
    "numpy>=1.24.0" 
] 

[project.optional-dependencies] 
dev = ["pytest>=7.0", "black>=23.0"] 
[project.urls] 
Homepage = "https://github.com/yourusername/my-package" 
"Bug Tracker" = "https://github.com/yourusername/my-package/issues" 

[project.scripts] 
my-tool = "my_package.cli:main" 

classifiers = [ 
    "Programming Language :: Python :: 3", 
    "License :: OSI Approved :: MIT License", 
] 

Key sections explained: 

  • [build-system]: Specifies the build backend (setuptools) and its minimum version. 

  • [project]: Contains essential metadata: 

    • name: Your package name on PyPI (use lowercase with hyphens) 

    • version: Following semantic versioning (e.g., "0.1.0") 

    • description: Brief summary of what your package does 

    • authors: Maintainer information with name and email 

    • dependencies: List of required packages with version constraints (e.g., >=2.28.0 means version 2.28.0 or higher). 

  • [project.optional-dependencies]: Additional dependencies for specific use cases: 

    • dev: Development tools like pytest for testing and black for code formatting 

    • You can add other groups like docs, test, or extras 

  • [project.urls]: Important links that appear on your PyPI package page: 

    • Homepage: Your project's main website or GitHub repository 

    • Bug Tracker: Where users can report issues (usually GitHub Issues) 

    • You can also add: "Documentation", "Source Code", "Changelog" 

  • [project.scripts]: Creates command-line tools that users can run after installing your package:  

    • Format: command-name = "package.module:function" 

    • Example: my-tool = "my_package.cli:main" creates a my-tool command that calls the main() function in my_package/cli.py 

    • After installation, users can simply type my-tool in their terminal 

  • classifiers: Help users find your package on PyPI by categorizing it:  

    • Programming Language :: Python :: 3: Indicates Python 3 compatibility 

    • License :: OSI Approved :: MIT License: Specifies your license type 

    • Other useful classifiers: "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Topic :: Software Development :: Libraries" 

Building Your Package 

Python packages come in two main distribution formats: sdist and bdist. Source distribution (sdist) is a compressed archive of source code that requires user compilation and installation, is platform-independent, and has smaller file size. Built distribution (bdist) is a pre-compiled package for immediate installation with faster installation but may have system compatibility limitations and larger file size.  

To build your package: 

  1. First install the build tool 

pip install build 

  1. Then build your package 

python3 -m build 

This creates both source and built distributions in the dist directory. 

 

Wheels are Python's standard pre-built packages that include Python source code, natively compiled code, and metadata. Benefits include faster installation (no compilation needed), no build toolchain headaches, reliable installation, easier dependency management, and works on multiple platforms including specialized systems like z/OS. 

Troubleshooting Build issues

Common errors: 

  1. "No module named 'build'": Install build package: pip install build 

  1. "Package directory not found": Check [tool.setuptools.packages.find] in pyproject.toml 

  1. "Invalid version": Ensure version follows semantic versioning (e.g., "0.1.0") 

Version Management 

Use semantic versioning following the major.minor.patch format (e.g., 1.2.3): 

  1. Major: Breaking changes (1.0.0 → 2.0.0) 

  1. Minor: New features, backward compatible (1.0.0 → 1.1.0) 

  1. Patch: Bug fixes (1.0.0 → 1.0.1) 

Documentation and Maintenance 

Good documentation and maintenance practices are essential for package adoption and long-term success. 

 

README.md

Your README.md appears on your PyPI package page and should include: project description, installation instructions (`pip install my-package`), quick start code example, key features list, link to full documentation, and license information. Keep it concise - users should understand what your package does and how to use it within 30 seconds. 

 

Licensing

Choose an open-source license and add a LICENSE file to your project root. Popular options include MIT (permissive, allows commercial use), Apache 2.0 (includes patent protection), and GPL (requires derivatives to be open source). Get license text from https://choosealicense.com/ and update the copyright year and name. 

Changelog Management

Maintain a CHANGELOG.md file to track changes between versions. Use this format:

# Changelog 

## [0.2.0] - 2026-06-15 
### Added 
- New feature X 
### Fixed 
- Bug in Y function 

## [0.1.0] - 2026-06-01 
### Added 
- Initial release 

Categories: Added (new features), Fixed (bug fixes), Changed (modifications), Deprecated (soon-to-be removed), Removed (deleted features), Security (vulnerability fixes).

Deployment 

The final step is deploying your package. As mentioned earlier, you can publish to PyPI for public distribution or use a private repository for internal use. Choose the deployment method that aligns with your project's needs, security requirements, and target audience. Both options support standard pip installation once properly configured. 

 

Conclusion 

Building and publishing Python packages involves careful planning and execution across multiple stages. By following these steps - setting up a proper project structure, writing tests, configuring your package correctly, and publishing to PyPI - you contribute to the Python ecosystem while making your code accessible to developers worldwide. 


Want to learn more about using Python on z/OS? Visit our Python Portal for a convenient way to access more resources.

 

0 comments
47 views

Permalink