Home Blog Embark on Python journey
Python

Embark on Python journey

Mastering classes, abstraction, packages and Poetry. A guide for structuring Python projects — from version management to FastAPI.

Embark on your Python journey: mastering classes, abstraction, packages, and Poetry for aspiring developers.

Introduction

In this guide, aspiring developers will explore the foundational elements of Python that are crucial for building robust applications. From understanding classes and abstraction to organizing code with packages and modules, you’ll gain practical insights into structuring your projects effectively.

Package and dependency management

Python package management is the process of managing the dependencies and packages used by Python projects. This includes installing, upgrading, and removing packages, as well as keeping track of their versions, requirements and dealing with python versions. Popular tools: Pyenv, Poetry, UV, Conda.

A — Manage Python version with Pyenv

Pyenv lets you manage multiple Python versions on your machine.

Installation

curl https://pyenv.run | bash

List and install versions

pyenv install --list
pyenv install 3.13.0
pyenv versions
pyenv global 3.13.0
python -V

B — Manage Python projects dependency and packaging with Poetry

Installation

curl -sSL https://install.python-poetry.org | python3 -

Basic usage

poetry new poetry-demo
poetry-demo
├── README.md
├── poetry_demo
│   └── __init__.py
├── pyproject.toml
└── tests
    └── __init__.py

pyproject.toml:

[tool.poetry]
name = "poetry-demo"
version = "0.1.0"
description = ""
authors = ["James Kokou GAGLO <[email protected]>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

Dependencies and virtual environments

poetry add playwright
poetry env use python3.12
poetry env list
poetry env info

Poe — task management

pip install poethepoet
[tool.poe.tasks]
py-version = "python --version"
poetry poe py-version

C — Python module and package

A module is a single file containing Python code. A package is a directory containing one or more modules and a special __init__.py file.

poetry-demo
├── poetry_demo
│   ├── __init__.py
│   ├── main.py
│   ├── module1.py
│   ├── subpackage1
│   │   ├── __init__.py
│   │   └── module2.py
│   └── subpackage2
│       ├── __init__.py
│       └── module3.py
# main.py
import module1
from subpackage1 import module2
from subpackage2 import module3

print(module1.func1())
print(module2.func2())
print(module3.func3())

D — Python and OOP concepts

Inheritance

class Animal:
    def speak(self):
        return "I make a sound."

class Dog(Animal):
    def speak(self):
        return "Woof!"

dog = Dog()
print(dog.speak())  # "Woof!"

Use super() to access parent class methods.

Polymorphism

class Cat:
    def speak(self):
        return "Meow!"

class Dog:
    def speak(self):
        return "Woof!"

animals = [Cat(), Dog()]
for animal in animals:
    print(animal.speak())

Abstraction

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return "Woof!"

Class and static methods

class MyClass:
    @classmethod
    def from_string(cls, arg):
        return cls(arg)

    @staticmethod
    def is_valid(arg):
        return arg > 0

E — Pydantic: data validation

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str = None
user_data = {"name": "Alice", "age": 30}
try:
    user = User(**user_data)
    print(user.name, user.age, user.email)
except ValueError as e:
    print(e)

Custom validator:

from pydantic import BaseModel, validator

class User(BaseModel):
    name: str
    age: int

    @validator('age')
    def check_age(cls, value):
        if value < 0:
            raise ValueError("Age must be positive")
        return value

Serialization:

user_dict = user.dict()
user_json = user.json()

F — Pydantic Settings: load settings and configs

from pydantic import BaseSettings, ValidationError

class MyAppSettings(BaseSettings):
    database_url: str
    debug_mode: bool = False
    max_connections: int = 10

    class Config:
        env_file = '.env'
        env_prefix = 'MYAPP_'
import os
os.environ['MYAPP_DATABASE_URL'] = 'sqlite:///mydb.sqlite'
os.environ['MYAPP_DEBUG_MODE'] = 'True'

settings = MyAppSettings()
print(settings.json(indent=2))

G — Click: command line interfaces

import click

@click.command()
@click.option('--name', default='World', help='The person to greet.')
def hello(name):
    """Simple program that greets NAME."""
    click.echo(f'Hello {name}!')

if __name__ == '__main__':
    hello()
python script.py --name James
python script.py

H — SQLAlchemy: SQL ORM

import datetime, uuid
from loguru import logger
from sqlalchemy import create_engine, func, MetaData
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import DeclarativeBase, mapped_column, Mapped, registry, Session
from typing import Type, TypeVar, Generic

_database = create_engine(settings.db_url, echo=settings.db_debug)
mapper_registry = registry()
my_metadata = MetaData()

T = TypeVar('T', bound='PsqlBaseModel')

class PsqlBaseModel(DeclarativeBase, Generic[T]):
    metadata = my_metadata
    id = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
    created_at: Mapped[datetime.datetime] = mapped_column(server_default=func.now())
    updated_at: Mapped[datetime.datetime] = mapped_column(server_default=func.now(), server_onupdate=func.now())

    @staticmethod
    def bulk_insert(objects):
        with Session(_database) as session:
            session.bulk_save_objects(objects)
            session.commit()

    @classmethod
    def get_by_hash(cls: Type[T], hash: str):
        with Session(_database) as session:
            return session.query(cls).filter(cls.hash == hash).first()

Create all tables:

PsqlBaseModel.metadata.create_all(_database)

Product model with PostgreSQL trigger:

class Product(PsqlBaseModel):
    __tablename__ = "products"
    name: Mapped[str] = mapped_column(nullable=True)
    hash: Mapped[str] = mapped_column(index=True)
    price: Mapped[int] = mapped_column(index=True, type_=DECIMAL(10,2), nullable=True, default=0)
    url: Mapped[str]
    website: Mapped[str]
    image: Mapped[str] = mapped_column(nullable=True)
    website_updated_at: Mapped[datetime.datetime]
    crawler_updated_at: Mapped[datetime.datetime]

I — Alembic: SQLAlchemy migrations

In progress.

J — FastAPI: web framework for building APIs

In progress.