Python: Uses Ruff linter

pull/428/head
Tpt 2 years ago committed by Thomas Tanon
parent fbcbd60c0e
commit 28def4001b
  1. 4
      .github/workflows/tests.yml
  2. 48
      python/generate_stubs.py
  3. 26
      python/pyproject.toml
  4. 1
      python/requirements.dev.txt
  5. 15
      python/tests/test_io.py
  6. 10
      python/tests/test_model.py
  7. 31
      python/tests/test_store.py

@ -228,7 +228,7 @@ jobs:
cache: pip
cache-dependency-path: '**/requirements.dev.txt'
- run: pip install -r python/requirements.dev.txt
- run: python -m black --check --diff --color .
- run: python -m black --check --diff --color .
working-directory: ./python
- run: maturin build -m python/Cargo.toml
- run: pip install --no-index --find-links=target/wheels/ pyoxigraph
@ -245,6 +245,8 @@ jobs:
working-directory: ./python
- run: python -m mypy generate_stubs.py tests --strict
working-directory: ./python
- run: python -m ruff check .
working-directory: ./python
python_msv:
runs-on: ubuntu-latest

@ -5,7 +5,7 @@ import inspect
import logging
import re
import subprocess
from typing import Set, List, Mapping, Any, Tuple, Union, Optional, Dict
from typing import Any, Dict, List, Mapping, Optional, Set, Tuple, Union
def _path_to_type(*elements: str) -> ast.AST:
@ -107,7 +107,7 @@ def class_stubs(
methods: List[ast.AST] = []
magic_methods: List[ast.AST] = []
for member_name, member_value in inspect.getmembers(cls_def):
current_element_path = element_path + [member_name]
current_element_path = [*element_path, member_name]
if member_name == "__init__":
try:
inspect.signature(cls_def) # we check it actually exists
@ -118,13 +118,14 @@ def class_stubs(
current_element_path,
types_to_import,
in_class=True,
)
] + methods
),
*methods,
]
except ValueError as e:
if "no signature found" not in str(e):
raise ValueError(
f"Error while parsing signature of {cls_name}.__init__: {e}"
)
f"Error while parsing signature of {cls_name}.__init_"
) from e
elif (
member_value == OBJECT_MEMBERS.get(member_name)
or BUILTINS.get(member_name, ()) is None
@ -256,7 +257,8 @@ def arguments_stub(
for match in re.findall(r"^ *:type *([a-z_]+): ([^\n]*) *$", doc, re.MULTILINE):
if match[0] not in real_parameters:
raise ValueError(
f"The parameter {match[0]} of {'.'.join(element_path)} is defined in the documentation but not in the function signature"
f"The parameter {match[0]} of {'.'.join(element_path)} "
"is defined in the documentation but not in the function signature"
)
type = match[1]
if type.endswith(", optional"):
@ -277,7 +279,8 @@ def arguments_stub(
for param in real_parameters.values():
if param.name != "self" and param.name not in parsed_param_types:
raise ValueError(
f"The parameter {param.name} of {'.'.join(element_path)} has no type definition in the function documentation"
f"The parameter {param.name} of {'.'.join(element_path)} "
"has no type definition in the function documentation"
)
param_ast = ast.arg(
arg=param.name, annotation=parsed_param_types.get(param.name)
@ -288,11 +291,13 @@ def arguments_stub(
default_ast = ast.Constant(param.default)
if param.name not in optional_params:
raise ValueError(
f"Parameter {param.name} of {'.'.join(element_path)} is optional according to the type but not flagged as such in the doc"
f"Parameter {param.name} of {'.'.join(element_path)} "
"is optional according to the type but not flagged as such in the doc"
)
elif param.name in optional_params:
raise ValueError(
f"Parameter {param.name} of {'.'.join(element_path)} is optional according to the documentation but has no default value"
f"Parameter {param.name} of {'.'.join(element_path)} "
"is optional according to the documentation but has no default value"
)
if param.kind == param.POSITIONAL_ONLY:
@ -329,14 +334,14 @@ def returns_stub(
if isinstance(builtin, tuple) and builtin[1] is not None:
return builtin[1]
raise ValueError(
f"The return type of {'.'.join(element_path)} has no type definition using :rtype: in the function documentation"
f"The return type of {'.'.join(element_path)} "
"has no type definition using :rtype: in the function documentation"
)
elif len(m) == 1:
return convert_type_from_doc(m[0], element_path, types_to_import)
else:
if len(m) > 1:
raise ValueError(
f"Multiple return type annotations found with :rtype: for {'.'.join(element_path)}"
)
return convert_type_from_doc(m[0], element_path, types_to_import)
def convert_type_from_doc(
@ -368,9 +373,9 @@ def parse_type_to_ast(
stack: List[List[Any]] = [[]]
for token in tokens:
if token == "(":
l: List[str] = []
stack[-1].append(l)
stack.append(l)
children: List[str] = []
stack[-1].append(children)
stack.append(children)
elif token == ")":
stack.pop()
else:
@ -435,13 +440,12 @@ def parse_type_to_ast(
def build_doc_comment(doc: str) -> ast.Expr:
lines = [l.strip() for l in doc.split("\n")]
lines = [line.strip() for line in doc.split("\n")]
clean_lines = []
for l in lines:
if l.startswith(":type") or l.startswith(":rtype"):
for line in lines:
if line.startswith((":type", ":rtype")):
continue
else:
clean_lines.append(l)
clean_lines.append(line)
return ast.Expr(value=ast.Constant("\n".join(clean_lines).strip()))

@ -28,3 +28,29 @@ Documentation = "https://pyoxigraph.readthedocs.io/"
Homepage = "https://pyoxigraph.readthedocs.io/"
Source = "https://github.com/oxigraph/oxigraph/tree/main/python"
Tracker = "https://github.com/oxigraph/oxigraph/issues"
[tool.ruff]
line-length = 120
select = [
"ARG",
"B",
"C40",
"E",
"F",
"FBT",
"I",
"ICN",
"ISC",
"N",
"PIE",
"PTH",
"RET",
"RUF",
"SIM",
"T10",
"TCH",
"TID",
"UP",
"W",
"YTT"
]

@ -2,4 +2,5 @@ black~=23.1
furo
maturin~=0.14.0
mypy~=1.0
ruff~=0.0.255
sphinx~=5.3

@ -1,9 +1,8 @@
import unittest
from io import StringIO, BytesIO, UnsupportedOperation
from io import BytesIO, StringIO, UnsupportedOperation
from tempfile import NamedTemporaryFile, TemporaryFile
from pyoxigraph import *
from pyoxigraph import Literal, NamedNode, Quad, Triple, parse, serialize
EXAMPLE_TRIPLE = Triple(
NamedNode("http://example.com/foo"), NamedNode("http://example.com/p"), Literal("1")
@ -55,9 +54,8 @@ class TestParse(unittest.TestCase):
)
def test_parse_io_error(self) -> None:
with self.assertRaises(UnsupportedOperation) as _:
with TemporaryFile("wb") as fp:
list(parse(fp, mime_type="application/n-triples"))
with self.assertRaises(UnsupportedOperation) as _, TemporaryFile("wb") as fp:
list(parse(fp, mime_type="application/n-triples"))
def test_parse_quad(self) -> None:
self.assertEqual(
@ -89,9 +87,8 @@ class TestSerialize(unittest.TestCase):
)
def test_serialize_io_error(self) -> None:
with self.assertRaises(UnsupportedOperation) as _:
with TemporaryFile("rb") as fp:
serialize([EXAMPLE_TRIPLE], fp, "text/turtle")
with self.assertRaises(UnsupportedOperation) as _, TemporaryFile("rb") as fp:
serialize([EXAMPLE_TRIPLE], fp, "text/turtle")
def test_serialize_quad(self) -> None:
output = BytesIO()

@ -1,6 +1,14 @@
import unittest
from pyoxigraph import *
from pyoxigraph import (
BlankNode,
DefaultGraph,
Literal,
NamedNode,
Quad,
Triple,
Variable,
)
XSD_STRING = NamedNode("http://www.w3.org/2001/XMLSchema#string")
XSD_INTEGER = NamedNode("http://www.w3.org/2001/XMLSchema#integer")

@ -1,10 +1,21 @@
import os
import unittest
from io import BytesIO, UnsupportedOperation
from pathlib import Path
from tempfile import NamedTemporaryFile, TemporaryDirectory, TemporaryFile
from typing import Any
from pyoxigraph import *
from pyoxigraph import (
BlankNode,
DefaultGraph,
NamedNode,
Quad,
QuerySolution,
QuerySolutions,
QueryTriples,
Store,
Triple,
Variable,
)
foo = NamedNode("http://foo")
bar = NamedNode("http://bar")
@ -259,13 +270,12 @@ class TestStore(unittest.TestCase):
fp.write(b"<http://foo> <http://bar> <http://baz> <http://graph>.")
store = Store()
store.load(file_name, mime_type="application/n-quads")
os.remove(file_name)
Path(file_name).unlink()
self.assertEqual(set(store), {Quad(foo, bar, baz, graph)})
def test_load_with_io_error(self) -> None:
with self.assertRaises(UnsupportedOperation) as _:
with TemporaryFile("wb") as fp:
Store().load(fp, mime_type="application/n-triples")
with self.assertRaises(UnsupportedOperation) as _, TemporaryFile("wb") as fp:
Store().load(fp, mime_type="application/n-triples")
def test_dump_ntriples(self) -> None:
store = Store()
@ -304,17 +314,14 @@ class TestStore(unittest.TestCase):
store = Store()
store.add(Quad(foo, bar, baz, graph))
store.dump(file_name, "application/n-quads")
with open(file_name, "rt") as fp:
file_content = fp.read()
self.assertEqual(
file_content,
Path(file_name).read_text(),
"<http://foo> <http://bar> <http://baz> <http://graph> .\n",
)
def test_dump_with_io_error(self) -> None:
with self.assertRaises(OSError) as _:
with TemporaryFile("rb") as fp:
Store().dump(fp, mime_type="application/rdf+xml")
with self.assertRaises(OSError) as _, TemporaryFile("rb") as fp:
Store().dump(fp, mime_type="application/rdf+xml")
def test_write_in_read(self) -> None:
store = Store()

Loading…
Cancel
Save