From Maven to pip: Python Tooling, Testing, and Packaging for Java Developers

Part 3 of the Python for Java Developers track. Last updated: October 2026.

You can write Python now — but can you ship it? Java gave you Maven, JUnit, and jar files as one coherent story. Python's story is equally coherent once you see the mapping: virtual environments instead of local repositories, pip instead of Maven, pytest instead of JUnit, and pyproject.toml instead of pom.xml. This post completes the track — and the whole Python roadmap.

Virtual environments: your local repository

Java developers never install dependencies globally — Maven scopes them per project. Python's equivalent is the virtual environment: an isolated directory holding one project's interpreter and packages. Create one per project, activate it, and everything you pip install stays inside:

# Create and activate a virtual environment (run in your shell):
#   python -m venv .venv
#   source .venv/bin/activate        # Windows: .venv\Scripts\activate
# Your prompt now shows (.venv) — pip installs go here, not system-wide.

import sys
print(sys.prefix)                    # points at .venv when activated
# /home/you/northwind-demo/.venv

pip and requirements: Maven dependencies, simplified

pip is Maven's dependency resolver without the XML. Install from PyPI (the Central Repository of Python), then freeze exact versions into requirements.txt so every machine — and every Docker build — installs the identical set:

# Shell, inside the activated venv:
#   pip install requests pandas streamlit
#   pip freeze > requirements.txt

# requirements.txt — the Python pom.xml (dependencies only):
# requests==2.32.3
# pandas==2.2.3
# streamlit==1.32.0

# Reproduce the exact environment anywhere:
#   pip install -r requirements.txt
import importlib.metadata
print(importlib.metadata.version("pip"))
# 24.2

pytest: JUnit without the boilerplate

pytest is JUnit reimagined: test functions instead of test classes, plain assert instead of assertion methods, and fixtures instead of @BeforeEach setup. Run with pytest, and failures show the values that broke — no expected:<…> but was:<…> decoding:

// Java + JUnit 5
@Test
void normalizesSku() {
    assertEquals("ABC-1", SkuUtil.normalize(" abc-1 "));
}
# Python + pytest — test_orders.py
from orders import normalize_sku

def test_normalizes_sku():            # plain function, plain assert
    assert normalize_sku(" abc-1 ") == "ABC-1"

def test_rejects_empty():
    assert normalize_sku("") is None

# Run: pytest -q
# 2 passed in 0.03s

Project layout: the Maven standard directory, Python edition

Maven's src/main/java convention has a Python twin. A professional small project looks like this — generate the skeleton from Python so every new project starts the same way:

from pathlib import Path

skeleton = [
    "pyproject.toml",                 # metadata + build config (the pom.xml)
    "requirements.txt",               # pinned runtime dependencies
    "README.md",
    "src/northwind/__init__.py",      # the importable package
    "src/northwind/pipeline.py",
    "tests/test_pipeline.py",         # pytest discovers test_*.py
]
for f in skeleton:
    p = Path("northwind-demo") / f
    p.parent.mkdir(parents=True, exist_ok=True)
    p.touch(exist_ok=True)
print("\n".join(sorted(str(Path("northwind-demo") / f) for f in skeleton)))

pyproject.toml: the pom.xml

Modern Python projects declare metadata in pyproject.toml — name, version, dependencies, and build backend in one TOML file. pip install -e . then installs your own package in editable mode, so import northwind works from anywhere in the project:

from pathlib import Path

Path("northwind-demo/pyproject.toml").write_text("""\
[project]
name = "northwind-demo"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = ["requests>=2.32", "pandas>=2.2"]

[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"
""")
print("pyproject.toml written — pip install -e . makes `import northwind` work")
# pyproject.toml written — pip install -e . makes `import northwind` work

Logging: java.util.logging's better-dressed cousin

Python's logging module mirrors what you know — loggers, levels, handlers — with one big convenience: logging.basicConfig configures a sensible default in a single call. Use module-level loggers exactly like Java's LoggerFactory.getLogger:

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(name)s %(levelname)s: %(message)s",
)
log = logging.getLogger(__name__)     # like LoggerFactory.getLogger(Class)

log.info("Fetching catalog page %d", 1)
log.warning("Rate limited — backing off %ds", 30)
# 2026-10-01 12:00:00 pipeline INFO: Fetching catalog page 1
# 2026-10-01 12:00:01 pipeline WARNING: Rate limited — backing off 30s

Key takeaways

  • One project, one virtual environment (python -m venv .venv) — never install project packages globally.
  • pip is Maven without the XML: pip install from PyPI, pip freeze > requirements.txt to pin.
  • pytest is JUnit without boilerplate: plain functions, plain assert, fixtures over @BeforeEach.
  • Standard layout: src/<package>/ + tests/ + pyproject.toml — generate it the same way every time.
  • pyproject.toml is the pom.xml; pip install -e . makes your package importable.
  • logging with basicConfig and module loggers covers 95% of what Java logging did for you.

You've completed the Python for Java Developers track — and the entire Python roadmap! Browse every tutorial on the Python topic page. Next on the roadmap: the Python Learning Roadmap 2026 hub post, tying all eight tracks into one guided path.

Comments

Popular posts from this blog

Java Banking Finance Services and Insurance (BFSI) domain interview questions

JSP Servlet Interview Questions For Freshers Series 1

Java program to check even or odd number