Python to Scala Library Mappings
JSON Handling
# Python
import json
data = {"name": "Alice", "age": 30}
json_str = json.dumps(data)
parsed = json.loads(json_str)
# With dataclass
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
# Python
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)
# Timezone
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
# Python
from pathlib import Path
import os
# Reading
with open("file.txt", "r") as f:
content = f.read()
lines = f.readlines()
# Writing
with open("file.txt", "w") as f:
f.write("Hello")
# Path operations
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
# Python
import re
pattern = r"\d+"
text = "abc123def456"
# Search
match = re.search(pattern, text)
if match:
print(match.group())
# Find all
matches = re.findall(pattern, text)
# Replace
result = re.sub(pattern, "X", text)
# Split
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
# Python
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
# Python
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
# Python - pytest
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
# Python
import asyncio
async def fetch_data(url: str) -> str:
# async HTTP call
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
# Python
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
# Python
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) |