Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
jodersky committed Jan 24, 2025
1 parent f718600 commit bb1ff5d
Show file tree
Hide file tree
Showing 15 changed files with 405 additions and 15 deletions.
26 changes: 26 additions & 0 deletions docs/modules/ROOT/pages/pythonlib/linting.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
= Linting Python Projects

include::partial$gtag-config.adoc[]

This page will discuss common topics around maintaining the code quality of Python
codebases using the Mill build tool


== Linting and Formatting with Ruff

Scalafix is a tool that analyzes your Scala source code, performing intelligent analyses and
code quality checks, and is often able to automatically fix the issues that it discovers.
It can also perform automated refactoring.

Mill supports Scalafix through the Mill-Scalafix third party module. See the module documentation
for more details:

* https://github.com/joan38/mill-scalafix

=== Linting

=== Formatting

include::partial$example/pythonlin/linting/1-ruff-format.adoc[]

== Code coverage
4 changes: 2 additions & 2 deletions example/pythonlib/basic/1-simple/build.mill
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package build
import mill._, pythonlib._

object foo extends PythonModule {
object foo extends PythonModule with CoverageModule {

def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }

def pythonDeps = Seq("Jinja2==3.1.4")

object test extends PythonTests with TestModule.Unittest
object test extends PythonTests with TestModule.Unittest with CoverageTests

}

Expand Down
63 changes: 63 additions & 0 deletions example/pythonlib/linting/1-ruff-format/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package build
import mill._, pythonlib._

object `package` extends RootModule with PythonModule with RuffModule

/** See Also: .scalafmt.conf */

// Mill supports code formatting via ruff[https://docs.astral.sh/ruff/] out of the box.
// You can reformat your project's code globally with `mill mill.scalalib.scalafmt.ScalafmtModule/` command,
// specific modules via `mill mill.scalalib.scalafmt.ScalafmtModule/ '{foo,bar}.sources`
// or only check the code's format with `+mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll`.
// By default, ScalaFmt checks for a `.scalafmt.conf` file at the root of repository.

/** Usage

> cat src/Foo.scala # initial poorly formatted source code
package foo
object Foo{
def main(args:
Array[String
]
):Unit=
{println("hello world")
}
}


> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
error: ...Found 1 misformatted files

> mill mill.scalalib.scalafmt.ScalafmtModule/

> cat src/Foo.scala
package foo
object Foo {
def main(args: Array[String]): Unit = { println("hello world") }
}

> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
Everything is formatted already

*/

// You can modify `.scalafmt.conf` to adjust the formatting as desired:

/** Usage
> echo "maxColumn: 50" >> .scalafmt.conf

> mill mill.scalalib.scalafmt.ScalafmtModule/

> cat src/Foo.scala
package foo
object Foo {
def main(args: Array[String]): Unit = {
println("hello world")
}
}
*/

// If entering the long fully-qualified module name `mill.scalalib.scalafmt.ScalafmtModule/`
// is tedious, you can add
// an xref:fundamentals/modules.adoc#_aliasing_external_modules[External Module Alias]
// to give it a shorter name that's easier to type
9 changes: 9 additions & 0 deletions example/pythonlib/linting/1-ruff-format/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import Self
class IntWrapper:
def __init__(self, x: int):
self.x=x

def plus(self, w:Self) -> Self:
return IntWrapper(self.x + w.x)

print(IntWrapper(2).plus(IntWrapper(3)).x)
69 changes: 69 additions & 0 deletions example/pythonlib/linting/2-ruff-config/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package build
import mill._, pythonlib._

object `package` extends RootModule with PythonModule with RuffModule {

def ruffConfig = Task.Source(millSourcePath / "ruff.toml")

def ruffOptions = Seq("--config", ruffConfig().path.toString)

}

/** See Also: .scalafmt.conf */

// Mill supports code formatting via https://scalameta.org/scalafmt/[scalafmt] out of the box.
// You can reformat your project's code globally with `mill mill.scalalib.scalafmt.ScalafmtModule/` command,
// specific modules via `mill mill.scalalib.scalafmt.ScalafmtModule/ '{foo,bar}.sources`
// or only check the code's format with `+mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll`.
// By default, ScalaFmt checks for a `.scalafmt.conf` file at the root of repository.

/** Usage

> cat src/Foo.scala # initial poorly formatted source code
package foo
object Foo{
def main(args:
Array[String
]
):Unit=
{println("hello world")
}
}


> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
error: ...Found 1 misformatted files

> mill mill.scalalib.scalafmt.ScalafmtModule/

> cat src/Foo.scala
package foo
object Foo {
def main(args: Array[String]): Unit = { println("hello world") }
}

> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
Everything is formatted already

*/

// You can modify `.scalafmt.conf` to adjust the formatting as desired:

/** Usage
> echo "maxColumn: 50" >> .scalafmt.conf

> mill mill.scalalib.scalafmt.ScalafmtModule/

> cat src/Foo.scala
package foo
object Foo {
def main(args: Array[String]): Unit = {
println("hello world")
}
}
*/

// If entering the long fully-qualified module name `mill.scalalib.scalafmt.ScalafmtModule/`
// is tedious, you can add
// an xref:fundamentals/modules.adoc#_aliasing_external_modules[External Module Alias]
// to give it a shorter name that's easier to type
1 change: 1 addition & 0 deletions example/pythonlib/linting/2-ruff-config/ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
indent-width=2
9 changes: 9 additions & 0 deletions example/pythonlib/linting/2-ruff-config/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from typing import Self
class IntWrapper:
def __init__(self, x: int):
self.x=x

def plus(self, w:Self) -> Self:
return IntWrapper(self.x + w.x)

print(IntWrapper(2).plus(IntWrapper(3)).x)
64 changes: 64 additions & 0 deletions example/pythonlib/linting/3-ruff-check/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package build
import mill._, pythonlib._

object `package` extends RootModule with PythonModule with RuffModule {
}

/** See Also: .scalafmt.conf */

// Mill supports code formatting via https://scalameta.org/scalafmt/[scalafmt] out of the box.
// You can reformat your project's code globally with `mill mill.scalalib.scalafmt.ScalafmtModule/` command,
// specific modules via `mill mill.scalalib.scalafmt.ScalafmtModule/ '{foo,bar}.sources`
// or only check the code's format with `+mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll`.
// By default, ScalaFmt checks for a `.scalafmt.conf` file at the root of repository.

/** Usage

> cat src/Foo.scala # initial poorly formatted source code
package foo
object Foo{
def main(args:
Array[String
]
):Unit=
{println("hello world")
}
}


> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
error: ...Found 1 misformatted files

> mill mill.scalalib.scalafmt.ScalafmtModule/

> cat src/Foo.scala
package foo
object Foo {
def main(args: Array[String]): Unit = { println("hello world") }
}

> mill mill.scalalib.scalafmt.ScalafmtModule/checkFormatAll
Everything is formatted already

*/

// You can modify `.scalafmt.conf` to adjust the formatting as desired:

/** Usage
> echo "maxColumn: 50" >> .scalafmt.conf

> mill mill.scalalib.scalafmt.ScalafmtModule/

> cat src/Foo.scala
package foo
object Foo {
def main(args: Array[String]): Unit = {
println("hello world")
}
}
*/

// If entering the long fully-qualified module name `mill.scalalib.scalafmt.ScalafmtModule/`
// is tedious, you can add
// an xref:fundamentals/modules.adoc#_aliasing_external_modules[External Module Alias]
// to give it a shorter name that's easier to type
4 changes: 4 additions & 0 deletions example/pythonlib/linting/3-ruff-check/src/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import os

def doit(x: int):
print(f"")
56 changes: 56 additions & 0 deletions example/pythonlib/linting/4-coverage/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package build
import mill._, scalalib._
import $ivy.`com.lihaoyi::mill-contrib-scoverage:`

import mill.contrib.scoverage._

object `package` extends RootModule with ScoverageModule {
def scoverageVersion = "2.1.0"
def scalaVersion = "2.13.11"
def ivyDeps = Agg(
ivy"com.lihaoyi::scalatags:0.12.0",
ivy"com.lihaoyi::mainargs:0.6.2"
)

object test extends ScoverageTests /*with TestModule.Utest */ {
def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.8.4")
def testFramework = "utest.runner.Framework"
}
}

// This is a basic Mill build for a single `ScalaModule`, enhanced with
// Scoverage plugin. The root module extends the `ScoverageModule` and
// specifies the version of scoverage version to use here: `2.1.0`. This
// version can be changed if there is a newer one. Now you can call the
// scoverage tasks to produce coverage reports.
// The sub test module extends `ScoverageTests` to transform the
// execution of the various testXXX tasks to use scoverage and produce
// coverage data.
// This lets us perform the coverage operations but before that you
// must first run the test.
// `./mill test` then `./mill scoverage.consoleReport` and get your
// coverage into your console output.

/** Usage

> ./mill test # Run the tests and produce the coverage data
...
+ foo.FooTests...simple ... <h1>hello</h1>
+ foo.FooTests...escaping ... <h1>&lt;hello&gt;</h1>

> ./mill resolve scoverage._ # List what tasks are available to run from scoverage
...
scoverage.consoleReport
...
scoverage.htmlReport
...
scoverage.xmlCoberturaReport
...
scoverage.xmlReport
...

> ./mill scoverage.consoleReport
...
Statement coverage.: 16.67%
Branch coverage....: 100.00%
*/
16 changes: 16 additions & 0 deletions example/pythonlib/linting/4-coverage/src/Foo.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package foo
import scalatags.Text.all._
import mainargs.{main, ParserForMethods}

object Foo {
def generateHtml(text: String) = {
h1(text).toString
}

@main
def main(text: String) = {
println(generateHtml(text))
}

def main(args: Array[String]): Unit = ParserForMethods(this).runOrExit(args)
}
17 changes: 17 additions & 0 deletions example/pythonlib/linting/4-coverage/test/src/FooTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package foo
import utest._

object FooTests extends TestSuite {
def tests = Tests {
test("simple") {
val result = Foo.generateHtml("hello")
assert(result == "<h1>hello</h1>")
result
}
test("escaping") {
val result = Foo.generateHtml("<hello>")
assert(result == "<h1>&lt;hello&gt;</h1>")
result
}
}
}
26 changes: 26 additions & 0 deletions pythonlib/src/mill/pythonlib/CoverageModule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package mill.pythonlib

import mill._

/**
* Code coverage
*/
trait CoverageModule extends PythonModule {

trait CoverageTests extends PythonTests {
override def pythonToolDeps = Task {
super.pythonToolDeps() ++ Seq("coverage>=7.6.9")
}

override def pythonOptions = Task {
Seq("-m", "coverage", "run") ++ super.pythonOptions()
}

def report = Task {
// testCached()
}

}


}
2 changes: 1 addition & 1 deletion pythonlib/src/mill/pythonlib/PythonModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ trait PythonModule extends PipModule with TaskModule { outer =>
command0 = pythonExe().path.toString,
options = pythonOptions(),
env0 = runnerEnvTask() ++ forkEnv(),
workingDir0 = Task.workspace
workingDir0 = Task.dest
)
}

Expand Down
Loading

0 comments on commit bb1ff5d

Please sign in to comment.