Building Python with Mill
This page contains a quick introduction to getting start with using Mill to build a simple python program. We will walk through a series of Mill builds of increasing complexity to show you the key features and usage of the Mill build tool.
The other pages of this section on python go into more depth into individual features, with more examples of how to use Mill for python and more details of how the Mill build tool works. They aren’t intended to be read comprehensively top-to-bottom, but rather looked up when you have a particular interest e.g. in testing, linting, publishing, and so on.
Simple Python Module
package build
import mill._, pythonlib._
object foo extends PythonModule {
def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }
def pythonDeps = Seq("Jinja2==3.1.4")
object test extends PythonTests with TestModule.Unittest
}
This is a basic Mill build for a single PythonModule
, with one
dependency and a test suite using the Unittest
Library.
build.mill foo/ src/ foo/foo.py resources/ ... test/ src/ foo/test.py out/foo/ run.json run.dest/ ... test/ run.json run.dest/ ...
This example project uses one dependency - Jinja2 for HTML rendering and uses it to wrap a given input string in HTML templates with proper escaping.
Typical usage of a PythonModule
is shown below:
> ./mill resolve foo._ # List what tasks are available to run
foo.bundle
...
foo.console
...
foo.run
...
foo.test
...
foo.typeCheck
> ./mill inspect foo.typeCheck # Show documentation and inputs of a task
...
foo.typeCheck(PythonModule...)
Run a typechecker on this module.
Inputs:
foo.pythonExe
foo.transitivePythonPath
foo.sources
...
> ./mill foo.typeCheck # TypeCheck the Python Files and notify errors
Success: no issues found in 1 source file
> ./mill foo.run --text "Hello Mill" # run the main method with arguments
<h1>Hello Mill</h1>
> ./mill foo.test
...
test_escaping (test.TestScript...) ... ok
test_simple (test.TestScript...) ... ok
...
----------------------------------------------------------------------
Ran 2 tests...
OK
...
> ./mill show foo.bundle # Creates Bundle for the python file
".../out/foo/bundle.dest/bundle.pex"
> out/foo/bundle.dest/bundle.pex --text "Hello Mill" # running the PEX binary outside of Mill
<h1>Hello Mill</h1>
> sed -i.bak 's/print(main())/print(maaain())/g' foo/src/foo.py
> ./mill foo.typeCheck # if we make a typo in a method name, mypy flags it
error: ...Name "maaain" is not defined...
Custom Build Logic
package build
import mill._, pythonlib._
object foo extends PythonModule {
def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }
/** All Python source files in this module, recursively from the source directories.*/
def allSourceFiles: T[Seq[PathRef]] = Task {
sources().flatMap(src => os.walk(src.path).filter(_.ext == "py").map(PathRef(_)))
}
/** Total number of lines in module source files */
def lineCount = Task {
allSourceFiles().map(f => os.read.lines(f.path).size).sum
}
/** Generate resources using lineCount of sources */
override def resources = Task {
val resourcesDir = Task.dest / "resources"
os.makeDir.all(resourcesDir)
os.write(resourcesDir / "line-count.txt", "" + lineCount())
super.resources() ++ Seq(PathRef(Task.dest))
}
object test extends PythonTests with TestModule.Unittest
}
Multi-Module Project
package build
import mill._, pythonlib._
trait MyModule extends PythonModule {
object test extends PythonTests with TestModule.Unittest
}
object foo extends MyModule {
def moduleDeps = Seq(bar)
def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }
}
object bar extends MyModule {
def mainScript = Task.Source { millSourcePath / "src" / "bar.py" }
def pythonDeps = Seq("Jinja2==3.1.4")
}
build.mill foo/ src/ foo.py test/ src/ test.py bar/ src/ bar.py test/ src/ test.py out/ foo/ run.json run.dest/ ... bar/ run.json run.dest/ ... test/ run.json run.dest/ ...