lolgab/snipy
{ "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.
Getting Started
Section titled “Getting Started”Add the following to your Scala Native Project:
import scala.sys.process._nativeLinkingOptions ++= "python3-config --ldflags".!!.split(' ').filter(_.nonEmpty).map(_.trim).toSeqlibraryDependencies += "com.github.lolgab" %%% "snipy" % "0.0.2"then you can call your first Python function :)
import snipy._ // import to enable snipy implicitsimport 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. }}Giter8 template
Section titled “Giter8 template”You can use the giter8 template to create an empty project:
sbt new lolgab/snipy.g8How it works
Section titled “How it works”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.
Writing facades
Section titled “Writing facades”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 _BarThis 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.