| name | python-scala-libraries |
| description | Guide for mapping common Python libraries and idioms to Scala equivalents. Use when converting Python code that uses standard library modules (json, datetime, os, re, logging) or needs equivalent Scala libraries for HTTP, testing, or async operations. |
Python to Scala Library Mappings
JSON Handling
import json
data = {"name": "Alice", "age": 30}
json_str = json.dumps(data)
parsed = json.loads(json_str)
from dataclasses import dataclass, asdict
@dataclass
class Person:
name: str
age: int
person = Person("Alice", 30)
json.dumps(asdict(person))
// Scala - circe (most popular)
import io.circe._
import io.circe.generic.auto._
import io.circe.syntax._
import io.circe.parser._
case class Person(name: String, age: Int)
val person = Person("Alice", 30)
val jsonStr: String = person.asJson.noSpaces
val parsed: Either[Error, Person] = decode[Person](jsonStr)
// Scala - play-json
import play.api.libs.json._
case class Person(name: String, age: Int)
implicit val personFormat: Format[Person] = Json.format[Person]
val json = Json.toJson(person)
val parsed = json.as[Person]
Date and Time
from datetime import datetime, date, timedelta
import pytz
now = datetime.now()
today = date.today()
specific = datetime(2024, 1, 15, 10, 30)
formatted = now.strftime("%Y-%m-%d %H:%M:%S")
parsed = datetime.strptime("2024-01-15", "%Y-%m-%d")
tomorrow = now + timedelta(days=1)
utc_now = datetime.now(pytz.UTC)
// Scala - java.time (recommended)
import java.time._
import java.time.format.DateTimeFormatter
val now = LocalDateTime.now()
val today = LocalDate.now()
val specific = LocalDateTime.of(2024, 1, 15, 10, 30)
val formatted = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
val parsed = LocalDate.parse("2024-01-15")
val tomorrow = now.plusDays(1)
// Timezone
val utcNow = ZonedDateTime.now(ZoneOffset.UTC)
val instant = Instant.now()
File Operations
from pathlib import Path
import os
with open("file.txt", "r") as f:
content = f.read()
lines = f.readlines()
with open("file.txt", "w") as f:
f.write("Hello")
path = Path("dir/file.txt")
path.exists()
path.parent
path.name
path.suffix
list(Path(".").glob("*.txt"))
os.makedirs("new/dir", exist_ok=True)
// Scala - java.nio.file (standard) + scala.io
import java.nio.file.{Files, Path, Paths}
import scala.io.Source
import scala.util.Using
// Reading
val content = Source.fromFile("file.txt").mkString
Using(Source.fromFile("file.txt")) { source =>
source.getLines().toList
}
// Writing
Files.writeString(Paths.get("file.txt"), "Hello")
// Path operations
val path = Paths.get("dir/file.txt")
Files.exists(path)
path.getParent
path.getFileName
// Extension: path.toString.split('.').lastOption
Files.createDirectories(Paths.get("new/dir"))
// os-lib (better alternative)
// import os._
// os.read(os.pwd / "file.txt")
// os.write(os.pwd / "file.txt", "Hello")
Regular Expressions
import re
pattern = r"\d+"
text = "abc123def456"
match = re.search(pattern, text)
if match:
print(match.group())
matches = re.findall(pattern, text)
result = re.sub(pattern, "X", text)
parts = re.split(r"\s+", "a b c")
// Scala
val pattern = """\d+""".r
val text = "abc123def456"
// Search
pattern.findFirstIn(text) match {
case Some(m) => println(m)
case None => ()
}
// Find all
val matches = pattern.findAllIn(text).toList
// Replace
val result = pattern.replaceAllIn(text, "X")
// Split
val parts = """\s+""".r.split("a b c").toList
// Pattern matching with regex
val datePattern = """(\d{4})-(\d{2})-(\d{2})""".r
"2024-01-15" match {
case datePattern(year, month, day) => s"$year/$month/$day"
case _ => "no match"
}
HTTP Requests
import requests
response = requests.get("https://api.example.com/data")
data = response.json()
response = requests.post(
"https://api.example.com/data",
json={"key": "value"},
headers={"Authorization": "Bearer token"}
)
// Scala - sttp (recommended)
import sttp.client3._
import sttp.client3.circe._
import io.circe.generic.auto._
val backend = HttpURLConnectionBackend()
val response = basicRequest
.get(uri"https://api.example.com/data")
.send(backend)
val postResponse = basicRequest
.post(uri"https://api.example.com/data")
.body("""{"key": "value"}""")
.header("Authorization", "Bearer token")
.send(backend)
Logging
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message", exc_info=True)
// Scala - scala-logging with logback
import com.typesafe.scalalogging.LazyLogging
class MyClass extends LazyLogging {
logger.debug("Debug message")
logger.info("Info message")
logger.warn("Warning message")
logger.error("Error message", exception)
}
// Alternative: slf4j directly
import org.slf4j.LoggerFactory
val logger = LoggerFactory.getLogger(getClass)
Testing
import pytest
def test_addition():
assert 1 + 1 == 2
@pytest.fixture
def sample_data():
return {"key": "value"}
def test_with_fixture(sample_data):
assert sample_data["key"] == "value"
@pytest.mark.parametrize("input,expected", [
(1, 2),
(2, 4),
])
def test_double(input, expected):
assert input * 2 == expected
// Scala - ScalaTest
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class MySpec extends AnyFlatSpec with Matchers {
"Addition" should "work" in {
1 + 1 shouldEqual 2
}
it should "handle negative numbers" in {
-1 + 1 shouldEqual 0
}
}
// Table-driven tests
import org.scalatest.prop.TableDrivenPropertyChecks._
val examples = Table(
("input", "expected"),
(1, 2),
(2, 4)
)
forAll(examples) { (input, expected) =>
input * 2 shouldEqual expected
}
// Scala - munit (simpler alternative)
class MySuite extends munit.FunSuite {
test("addition") {
assertEquals(1 + 1, 2)
}
}
Async/Concurrent Operations
import asyncio
async def fetch_data(url: str) -> str:
await asyncio.sleep(1)
return "data"
async def main():
results = await asyncio.gather(
fetch_data("url1"),
fetch_data("url2"),
)
// Scala - Future (standard)
import scala.concurrent.{Future, ExecutionContext}
import scala.concurrent.ExecutionContext.Implicits.global
def fetchData(url: String): Future[String] = Future {
Thread.sleep(1000)
"data"
}
val results: Future[List[String]] = Future.sequence(List(
fetchData("url1"),
fetchData("url2")
))
// Scala - cats-effect IO (preferred for FP)
import cats.effect.IO
import cats.syntax.parallel._
def fetchData(url: String): IO[String] = IO.sleep(1.second) *> IO.pure("data")
val results: IO[List[String]] = List(
fetchData("url1"),
fetchData("url2")
).parSequence
Configuration
import os
from dataclasses import dataclass
@dataclass
class Config:
db_host: str = os.getenv("DB_HOST", "localhost")
db_port: int = int(os.getenv("DB_PORT", "5432"))
// Scala - pureconfig
import pureconfig._
import pureconfig.generic.auto._
case class Config(dbHost: String, dbPort: Int)
val config = ConfigSource.default.loadOrThrow[Config]
// application.conf (HOCON format)
// db-host = "localhost"
// db-host = ${?DB_HOST}
// db-port = 5432
Command Line Arguments
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--name", required=True)
parser.add_argument("--count", type=int, default=1)
args = parser.parse_args()
// Scala - scopt
import scopt.OParser
case class Config(name: String = "", count: Int = 1)
val builder = OParser.builder[Config]
val parser = {
import builder._
OParser.sequence(
opt[String]("name").required().action((x, c) => c.copy(name = x)),
opt[Int]("count").action((x, c) => c.copy(count = x))
)
}
OParser.parse(parser, args, Config()) match {
case Some(config) => // use config
case None => // arguments are bad
}
// Scala - decline (FP style)
import com.monovore.decline._
val nameOpt = Opts.option[String]("name", "Name")
val countOpt = Opts.option[Int]("count", "Count").withDefault(1)
val command = Command("app", "Description") {
(nameOpt, countOpt).tupled
}
Build Tools Comparison
| Python | Scala |
|---|
| pip/poetry | sbt/mill |
| requirements.txt | build.sbt |
| pyproject.toml | build.sbt |
| setup.py | build.sbt |
| virtualenv | Project-local dependencies (automatic) |