Python Library Development

collinmutembei,micro-serviceslibraryclean-code

Introduction

Microservices are a popular architectural pattern for building modern, scalable and modular applications. In a microservice architecture, services communicate with each other through APIs using a well-defined contract. This contract is often expressed in terms of data schemas and operations on data. In this blog, I will describe how to develop a shared library in Python for sharing common data schemas and operations on data for a microservice architecture. This shared library will enable the different microservices to use the same data schema and data operations, promoting code reuse and consistency.

Designing the Shared Library

The first step in developing a shared library is to design the data schema and operations that will be used by the different microservices. The data schema should be designed to be extensible and easily modifiable. The operations should be designed to be efficient and easily reusable. Some examples of data schema and operations that can be shared in a microservice architecture include:

Data schema:

from pydantic import BaseModel, validator
 
class User(BaseModel):
    id: int
    username: str
    password: str
 
    @validator('username')
    def no_special_chars(cls, v):
        if any(char in "!@#$%^&*()-+_=~`[]{}|;:<>,.?/" for char in v):
            raise ValueError('special characters are not allowed')
        return v

Operations on data:

def hash_password(password: str) -> str:
    return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
 
def verify_password(password: str, hashed_password: str) -> bool:
    return bcrypt.checkpw(password.encode(), hashed_password.encode())

Developing the Shared Library

Once the data schema and operations have been designed, the shared library can be developed in Python. The library can be developed as a Python package, with each data schema and operation implemented as a separate module or class. The library should also have a version number, which should be updated whenever a new version of the library is released.

from setuptools import setup, find_packages
 
setup(
    name="shared_library",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "pydantic>=1.8.2",
        "bcrypt>=3.2.0",
    ],
)

The package can be installed using pip and can be imported into the different microservices. The different microservices can then use the shared library to define their data schema and perform operations on data.

from shared_library import User, hash_password, verify_password
 
user = User(id="1", username="alice", password="password")
hashed_password = hash_password(user.password)
is_valid_password = verify_password("password", hashed_password)
 

Versioning the Shared Library

It is important to version the shared library to enable the different microservices to use the same version of the library. A version bump in the shared library should trigger a downstream dependency update in the different microservices. This can be achieved using GitHub Actions.

The GitHub Actions workflow can be defined in a YAML file located in the .github/workflows directory of the shared library repository. The workflow should be triggered whenever a new tag is pushed to the repository. The workflow should then update the version number of the shared library, create a new release, and push the changes to the repository.

name: Bump version and create release
 
on:
  push:
    tags:
      - "*"
 
jobs:
  bump-version:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
        with:
          ref: ${{ github.ref }}
      - name: Setup Python
        uses: actions/setup-python@v2
        with:
          python-version: "3.9"
      - name: Install dependencies
        run: |
          pip install --upgrade pip
          pip install setuptools wheel twine
      - name: Bump version and create release
        run: |
          bumpversion patch --commit --tag
          git push --follow-tags
          python setup.py sdist bdist_wheel
          twine upload dist/*
          env:
          TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
          TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}

The bumpversion command is used to bump the version number of the shared library. The patch argument specifies that the patch version should be bumped. The --commit argument specifies that the version bump should be committed to the repository. The --tag argument specifies that a new tag should be created for the version bump.

The twine command is used to upload the new release to the Python Package Index (PyPI). The dist directory contains the source distribution (sdist) and binary distribution (bdist_wheel) of the shared library. The TWINE_USERNAME and TWINE_PASSWORD environment variables are used to authenticate with PyPI.

Using Dependabot for Dependency Updates

To make it easy to update the dependent repositories whenever the shared library is bumped, we can use Dependabot. Dependabot is a tool that automatically updates dependencies in your repository. It monitors your dependencies for any available updates and creates pull requests with the updated versions.

To use Dependabot, we need to create a configuration file in the .github/dependabot.yml directory of the dependent repositories. The configuration file specifies the dependencies that should be monitored for updates and how the updates should be applied.

version: 2
updates:
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"
    allow:
      - dependency-name: "shared_library"
    open-pull-requests-limit: 3

The configuration file specifies that the shared library should be monitored for updates. The schedule section specifies that updates should be checked daily. The allow section specifies that only the shared library should be updated. The open-pull-requests-limit section specifies that up to 3 pull requests can be opened at a time.

Whenever a new version of the shared library is released, Dependabot will create a pull request in the dependent repositories with the updated version. The pull request can be reviewed and merged, making it easy to keep the dependent repositories up to date with the shared library. This helps reduce the likelihood of compatibility issues and ensuring that the latest features and bug fixes are used.

Conclusion

In this blog, I have described how to develop a shared library for sharing common data schemas and operations on data for a microservice architecture. The shared library was developed in Python and versioned using GitHub Actions. The shared library enables the different microservices to use the same data schema and data operations, promoting code reuse and consistency.

© Collin Mutembei.RSS