DEV Community

Ukasha Mahir
Ukasha Mahir

Posted on

Python in 2024: From One‑Liners to Scalable Services

Introduction

Python’s reputation as a “glue” language hasn’t faded; it’s now a first‑class citizen in everything from data science to serverless back‑ends. In 2024 the ecosystem has matured enough that you can start with a quick script and, with a handful of modern features, scale that same code into a production‑ready service. This post walks through the most relevant language enhancements, practical patterns, and tooling that let you write clean, performant, and maintainable Python today.

Pro tip: Treat the examples as a blueprint—not a copy‑paste solution. Adjust paths, credentials, and error handling to fit your real‑world constraints.


1. Modern Language Features Worth Knowing

Feature Why it matters Quick example
Type hints (PEP 484) Improves IDE support, static analysis, and documentation. def fetch(url: str) -> dict[str, Any]: ...
Structural pattern matching (PEP 634) Replaces complex if‑elif chains with expressive match blocks. See the “Command dispatcher” snippet below.
Async/Await (PEP 492) Enables non‑blocking I/O, crucial for network‑bound workloads. async def get(url): ...
Assignment expressions (:=) Reduces boilerplate when you need a value both for a test and later use. if (data := await fetch(url)): ...
F‑strings with = (PEP 572) Debug-friendly string interpolation. print(f"{value=}")

These aren’t optional “nice‑to‑have” extras—they’re now mainstream, and most linters (e.g., ruff, flake8) flag missing type hints in public APIs.


2. Building a Real‑World CLI Tool

Let’s build a tiny command‑line utility called gh‑issues that fetches open issues from a GitHub repository, filters them by label, and prints a summary. The example showcases:

  • argparse for CLI parsing
  • httpx for async HTTP requests
  • pydantic for data validation (type‑safe models)
  • rich for pretty terminal output
# gh_issues.py
import argparse
import asyncio
from typing import List

import httpx
from pydantic import BaseModel, Field
from rich.console import Console
from rich.table import Table

API_URL = "https://api.github.com/repos/{owner}/{repo}/issues"


class Issue(BaseModel):
    number: int
    title: str
    labels: List[dict] = Field(default_factory=list)
    html_url: str


async def fetch_issues(owner: str, repo: str, token: str | None = None) -> List[Issue]:
    headers = {"Accept": "application/vnd.github+json"}
    if token:
        headers["Authorization"] = f"Bearer {token}"
    async with httpx.AsyncClient(headers=headers) as client:
        resp = await client.get(API_URL.format(owner=owner, repo=repo))
        resp.raise_for_status()
        return [Issue(**item) for item in resp.json()]


def filter_by_label(issues: List[Issue], label: str) -> List[Issue]:
    return [i for i in issues if any(l["name"] == label for l in i.labels)]


def display(issues: List[Issue]) -> None:
    console = Console()
    table = Table(title="GitHub Open Issues")
    table.add_column("#", style="cyan", justify="right")
    table.add_column("Title", style="magenta")
    table.add_column("Labels", style="green")
    table.add_column("URL", style="blue", overflow="fold")

    for issue in issues:
        lbls = ", ".join(l["name"] for l in issue.labels) or "none"
        table.add_row(str(issue.number), issue.title, lbls, issue.html_url)
    console.print(table)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="List open GitHub issues for a repo"
    )
    parser.add_argument("owner", help="GitHub owner or organization")
    parser.add_argument("repo", help="Repository name")
    parser.add_argument("-l", "--label", help="Filter by label")
    parser.add_argument("-t", "--token", help="Personal access token (optional)")
    return parser.parse_args()


async def main() -> None:
    args = parse_args()
    issues = await fetch_issues(args.owner, args.repo, args.token)
    if args.label:
        issues = filter_by_label(issues, args.label)
    display(issues)


if __name__ == "__main__":
    asyncio.run(main())
Enter fullscreen mode Exit fullscreen mode

Key takeaways

  • The function signatures are fully typed, which lets mypy or pyright catch mismatches early.
  • asyncio.run(main()) ensures the event loop is managed cleanly.
  • rich handles terminal rendering without any manual ANSI codes.

Run it like:

python gh_issues.py python/cpython -l documentation
Enter fullscreen mode Exit fullscreen mode

3. Testing with pytest and Hypothesis

Testing async code is straightforward with pytest-asyncio. Below is a minimal test suite that validates the filtering logic and mocks the HTTP call.

# tests/test_gh_issues.py
import asyncio
from unittest.mock import AsyncMock, patch

import pytest
from gh_issues import filter_by_label, fetch_issues, Issue


@pytest.fixture
def sample_issues():
    return [
        Issue(number=1, title="Bug A", labels=[{"name": "bug"}], html_url="https://..."),
        Issue(number=2, title="Feature B", labels=[{"name": "enhancement"}], html_url="https://..."),
    ]


def test_filter_by_label(sample_issues):
    bugs = filter_by_label(sample_issues, "bug")
    assert len(bugs) == 1
    assert bugs[0].title == "Bug A"


@pytest.mark.asyncio
async def test_fetch_issues_mock(sample_issues):
    mock_resp = AsyncMock()
    mock_resp.json.return_value = [i.dict() for i in sample_issues]
    mock_resp.raise_for_status.return_value = None

    with patch("httpx.AsyncClient.get", return_value=mock_resp):
        result = await fetch_issues("owner", "repo")
        assert len(result) == 2
        assert result[0].number == 1
Enter fullscreen mode Exit fullscreen mode

Run tests with:

pytest -q
Enter fullscreen mode Exit fullscreen mode

For property‑based testing, Hypothesis can generate random issue payloads to ensure your parser never crashes on unexpected fields.


4. Packaging with Poetry

Poetry has become the de‑facto tool for dependency management and publishing. Here’s a quick pyproject.toml snippet for our CLI:

[tool.poetry]
name = "gh-issues"
version = "0.1.0"
description = "CLI to list GitHub issues"
authors = ["Your Name <you@example.com>"]
license = "MIT"

[tool.poetry.dependencies]
python = "^3.11"
httpx = "^0.27.0"
pydantic = "^2.5.0"
rich = "^13.7.0"

[tool.poetry.dev-dependencies]
pytest = "^8.1.0"
pytest-asyncio = "^0.23.0"
hypothesis = "^6.100.0"

[tool.poetry.scripts]
gh-issues = "gh_issues:main"
Enter fullscreen mode Exit fullscreen mode

Run poetry install to create an isolated virtual environment, and poetry build to generate wheels ready for PyPI.


5. Performance Tweaks You Can Apply Today

Situation Fastest pattern Why it wins
Small loops List comprehension C‑level bytecode, no function call overhead.
Heavy CPU work multiprocessing + concurrent.futures Bypasses the GIL by using separate processes.
Repeated expensive calls functools.lru_cache Memoizes results, saving network round‑trips.
Large data transformations numpy or pandas vectorization Leverages compiled C extensions.

A quick illustration of caching an HTTP call:

from functools import lru_cache
import httpx

@lru_cache(maxsize=128)
def get_user(username: str) -> dict:
    resp = httpx.get(f"https://api.github.com/users/{username}")
    resp.raise_for_status()
    return resp.json()
Enter fullscreen mode Exit fullscreen mode

Remember: caching works best for idempotent, read‑only data. Never cache mutable responses unless you know they’re safe.


6. Deploying the CLI as a Serverless Function

If you ever need a one‑off endpoint, the same code can be wrapped in an AWS Lambda handler:

# lambda_handler.py
import json
from gh_issues import fetch_issues, filter_by_label

async def handler(event, context):
    body = json.loads(event["body"])
    owner = body["owner"]
    repo = body["repo"]
    label = body.get("label")
    token = body.get("token")

    issues = await fetch_issues(owner, repo, token)
    if label:
        issues = filter_by_label(issues, label)

    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps([i.dict() for i in issues]),
    }
Enter fullscreen mode Exit fullscreen mode

Deploy with AWS SAM, Zappa, or Serverless Framework—all of which understand async handlers now.


Conclusion

Python in 2024 feels like a Swiss army knife: the language itself offers expressive syntax and modern constructs, while the surrounding ecosystem supplies battle‑tested tools for testing, packaging, and deployment. By embracing type hints, async/await, and the right CLI libraries, you can evolve a simple script into a maintainable, performant service without reinventing the wheel.


Recommended Tools

If you’re looking for a hassle‑free environment to develop and host Python apps, PythonAnywhere provides instant web‑app deployment with a built‑in console and MySQL support.

For a feature‑rich IDE that understands type hints, debugging, and refactoring, give JetBrains PyCharm Professional a try—it’s especially handy when working on larger codebases.

Happy coding!

Top comments (0)