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

JSP Servlet Interview Questions For Freshers Series 1

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

Java program to check even or odd number