Skip to content
Oeiuwq Faith Blog OpenSource Porfolio

lolgab/snipy

Call python code from Scala Native!

lolgab/snipy.json
{
"createdAt": "2019-04-01T14:44:57Z",
"defaultBranch": "master",
"description": "Call python code from Scala Native!",
"fullName": "lolgab/snipy",
"homepage": "",
"language": "Scala",
"name": "snipy",
"pushedAt": "2021-08-01T14:33:06Z",
"stargazersCount": 19,
"topics": [],
"updatedAt": "2024-12-20T09:29:04Z",
"url": "https://github.com/lolgab/snipy"
}

This project was deprecated in favor of ScalaPy. Use ScalaPy instead.

Snipy is a bridge to call Python code from Scala Native. It interfaces directly with Python C Api without ffi at (almost) zero cost.

Add the following to your Scala Native Project:

import scala.sys.process._
nativeLinkingOptions ++= "python3-config --ldflags".!!.split(' ').filter(_.nonEmpty).map(_.trim).toSeq
libraryDependencies += "com.github.lolgab" %%% "snipy" % "0.0.2"

then you can call your first Python function :)

import snipy._ // import to enable snipy implicits
import snipy.dynamic._ // enables dynamic interface
object Main {
def main(args: Array[String]): Unit = PyZone { implicit z =>
// You need to wrap Python call in a PyZone
// when zone block ends every PyObject is
// no more usable
val math = module("math") // equals to python's "import math"
val res = math.log10(2.0) // calls dynamically the log10 function passing a Double converted to PyObject
println(res) // calls the "str" python function as toString.
}
}

You can use the giter8 template to create an empty project:

sbt new lolgab/snipy.g8

The conversions are managed by two typeclasses:

trait AsPython[T, U <: PyObject] {
def asPython(t: T)(implicit z: PyZone): U
}

and

trait AsScala[T <: PyObject, U] {
def asScala(t: T): U
}

You can implement new instances to convert directly from a Python type to a Scala Type without using default types’ provided instances.

You can define a hierarchy for python types this way:

object myfacade {
private[myfacade] trait _Foo
type Foo = PyObject with _Foo
private[myfacade] trait _Bar
type Bar = Foo with _Bar

This way you can say that Bar extends Foo.

You need to define a module. Not having a PyZone at facade site you can use the default leaking zone where Python objects are never freed:

val pythonLib = module("pythonLib")(PyZone.leakingZone)

now you can define a Scala function for every python function you need:

def pyFunction(foo: String): List[Int] = PyZone { implicit z =>
// You can define a local PyZone because you don't
// need the intermediate Python Objects (result is a scala type)
Dyn(pythonLib).pyFunction(foo.asPython).as[PyList[PyLong]].asScala
// the as method casts to a specific python type
// so .asScala can call the correct typeclass instance
}

Then for every class you have to define an implicit class:

implicit class MyPyObjectOps(val o: MyPyObject) extends AnyVal {
def function(): Unit = PyZone { implicit z =>
Dyn(o).function()
}
}

Give a look to the facades folder for examples.