Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose Ruff's public API as a Python library #1407

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ walkdir = { version = "2.3.2" }
clearscreen = { version = "1.0.10" }
rayon = { version = "1.5.3" }
update-informer = { version = "0.5.0", default-features = false, features = ["pypi"], optional = true }
pyo3 = { version = "0.17.3", features = ["extension-module", "abi3", "abi3-py37", "anyhow"] }
pythonize = "0.17.0"

# https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support
# For (future) wasm-pack support
Expand Down
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
recursive-include ruff *
global-exclude __pycache__
global-exclude *.py[co]
13 changes: 5 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,18 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
]
author = "Charlie Marsh"
author_email = "charlie.r.marsh@gmail.com"
authors = [
{name = "Charlie Marsh", email= "charlie.r.marsh@gmail.com" }
]
description = "An extremely fast Python linter, written in Rust."
requires-python = ">=3.7"
version = "0.0.195"

[project.urls]
repository = "https://github.com/charliermarsh/ruff"

[build-system]
requires = ["maturin>=0.14,<0.15"]
build-backend = "maturin"

[tool.maturin]
bindings = "bin"
strip = true
requires = ["setuptools", "wheel", "setuptools-rust"]

[tool.ruff]

Expand Down
1 change: 1 addition & 0 deletions ruff/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from ._ruff import check # noqa: F401
12 changes: 12 additions & 0 deletions ruff/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Location:
row: int
column: int


class Message:
code: str
message: str
location: Location
end_location: Location

def check(contents: str | None, path: str | None, options: Mapping[str, Any] | None) -> list(Message): ...
Empty file added ruff/py.typed
Empty file.
50 changes: 50 additions & 0 deletions ruff/tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest

from ruff import check


class CheckTest(unittest.TestCase):
def test_something(self):
results = check(
"""
import os
import sys

print(os.environ['GREETING'])
"""
)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].code, "F401")
self.assertEqual(results[0].message, "`sys` imported but unused")
self.assertEqual(results[0].location.row, 3)
self.assertEqual(results[0].location.column, 7)
self.assertEqual(results[0].end_location.row, 3)
self.assertEqual(results[0].end_location.column, 10)

def test_options(self):
results = check(
"""
import sys

print("This is a really long text")
""",
options={"line-length": 10, "ignore": ["F401"]},
)
self.assertEqual(len(results), 1)
self.assertEqual(results[0].code, "E501")
self.assertEqual(results[0].message, "Line too long (35 > 10 characters)")
self.assertEqual(results[0].location.row, 4)
self.assertEqual(results[0].location.column, 10)
self.assertEqual(results[0].end_location.row, 4)
self.assertEqual(results[0].end_location.column, 35)

def test_invalid_options(self):
with self.assertRaisesRegex(
Exception,
"unknown field `line-count`, expected one of `allowed-confusables`, .*",
):
check("print(123)", options={"line-count": 10})


if __name__ == "__main__":
unittest.main()
32 changes: 11 additions & 21 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,13 @@
import sys

from setuptools import setup

sys.stderr.write(
"""
===============================
Unsupported installation method
===============================
ruff no longer supports installation with `python setup.py install`.
Please use `python -m pip install .` instead.
"""
from setuptools_rust import Binding, RustExtension, RustBin, Strip

setup(
name="ruff",
rust_extensions=[
RustExtension("ruff._ruff", binding=Binding.PyO3, strip=Strip.Debug, py_limited_api=True),
RustBin("ruff", strip=Strip.Debug),
],
packages=["ruff"],
# rust extensions are not zip safe, just like C-extensions.
zip_safe=False,
)
sys.exit(1)


# The below code will never execute, however GitHub is particularly
# picky about where it finds Python packaging metadata.
# See: https://github.com/github/feedback/discussions/6456
#
# To be removed once GitHub catches up.

setup(name="ruff", install_requires=[])
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ cfg_if! {

mod lib_native;
pub use lib_native::check;

mod lib_python;
pub use lib_python::_ruff;
} else {
mod lib_wasm;
pub use lib_wasm::check;
Expand Down
4 changes: 3 additions & 1 deletion src/lib_native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use crate::source_code_style::SourceCodeStyleDetector;
use crate::{directives, packages, resolver};

/// Load the relevant `Settings` for a given `Path`.
fn resolve(path: &Path) -> Result<Settings> {
/// TODO(rgerecke): Move this somewhere else as to not make it part of public
/// API
pub fn resolve(path: &Path) -> Result<Settings> {
if let Some(pyproject) = pyproject::find_settings_toml(path)? {
// First priority: `pyproject.toml` in the current `Path`.
resolver::resolve_settings(&pyproject, &Relativity::Parent, None)
Expand Down
Loading