Skip to content

Commit

Permalink
Add linter
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicals committed Oct 2, 2023
1 parent 5869eea commit 2cd23f7
Show file tree
Hide file tree
Showing 16 changed files with 64 additions and 39 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
run: pytest --cov --cov-report lcov
- name: Check typing
run: mypy
- name: Check style
run: ruff check --output-format=github .

- name: Upload coverage report
uses: coverallsapp/github-action@master
Expand Down
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ dev = [
"mypy ~= 1.4",
"pytest ~= 7.2",
"pytest-cov ~= 4.0",
"ruff",
]

[project.urls]
Expand Down Expand Up @@ -56,3 +57,13 @@ source = ["saulve"]

[tool.coverage.report]
skip_empty = true

[tool.ruff]
select = [
"E", # pycodestyle errors
"W", # pycodestyle warnings
"F", # pyflakes
"B", # flake8-bugbear
"C", # flake8-comprehension
"I", # isort
]
1 change: 0 additions & 1 deletion saulve/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from .app import App
from .puzzle import Puzzle


__all__ = ['App', 'Puzzle']
1 change: 0 additions & 1 deletion saulve/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .cli import cli


cli()
9 changes: 4 additions & 5 deletions saulve/app.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import sys

from .challenges.base import Challenge, ChallengeLoader
from .errors import ChallengeNotFound, SaulveError
from .import_module import append_module_path, import_instance
from .errors import SaulveError, ChallengeNotFound


__all__ = ['App', 'import_app']


class App:
def __init__(self) -> None:
self.loaders: dict[str, ChallengeLoader] = dict()
self.loaders: dict[str, ChallengeLoader] = {}

def register_challenge(
self,
Expand All @@ -36,10 +35,10 @@ def get_challenge(self, challenge_id: str) -> Challenge:
"""
try:
challenge = self.loaders[challenge_id]
except KeyError:
except KeyError as e:
raise ChallengeNotFound(
f"No challenge exists with id {challenge_id}"
)
) from e

return challenge.load()

Expand Down
17 changes: 10 additions & 7 deletions saulve/challenges/advent_of_code.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
"""Advent of code challenge implementation
"""

from pathlib import Path
import re
import types
from pathlib import Path
from typing import NamedTuple

from .base import Challenge, ChallengeLoader, PuzzleView
from saulve import Puzzle
from saulve.errors import PuzzleNotFound, ValidationError
from saulve.import_module import import_instance

from .base import Challenge, ChallengeLoader, PuzzleView

YEAR_REGEX = re.compile(r'^year_(?P<year>\d{4})$')
PUZZLE_REGEX = re.compile(r'^day_(?P<day>\d{1,2})\.py$')
Expand All @@ -31,7 +31,10 @@ def __init__(self, puzzles: list[AdventOfCodePuzzle]) -> None:

def find(self) -> list[PuzzleView]:
return [
PuzzleView(id=f'{puzzle.year} {puzzle.day:02}', name=puzzle.puzzle.name)
PuzzleView(
id=f'{puzzle.year} {puzzle.day:02}',
name=puzzle.puzzle.name,
)
for puzzle in self.puzzles
]

Expand All @@ -41,13 +44,13 @@ def get(self, *args: str) -> Puzzle:

try:
year = int(args[0])
except ValueError:
raise ValidationError(f"'{args[0]} is not a valid year.")
except ValueError as e:
raise ValidationError(f"'{args[0]} is not a valid year.") from e

try:
day = int(args[1])
except ValueError:
raise ValidationError(f"'{args[1]} is not a valid day.")
except ValueError as e:
raise ValidationError(f"'{args[1]} is not a valid day.") from e

for puzzle in self.puzzles:
if puzzle.year == year and puzzle.day == day:
Expand Down
2 changes: 1 addition & 1 deletion saulve/challenges/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import NamedTuple, Protocol

from ..errors import PuzzleNotFound, ValidationError
from ..puzzle import Puzzle


Expand All @@ -13,6 +12,7 @@ class PuzzleView(NamedTuple):
class Challenge(Protocol):
"""A collection of puzzle from a common source.
"""

def find(self) -> list[PuzzleView]:
"""Get all known puzzles"""

Expand Down
25 changes: 16 additions & 9 deletions saulve/challenges/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
"""

import logging
from pathlib import Path
import re
import types
from pathlib import Path
from typing import Optional

from saulve.errors import MissingAttribute, PuzzleNotFound, ValidationError
from saulve.puzzle import Puzzle
from saulve.import_module import import_instance
from .base import Challenge as BaseChallenge, ChallengeLoader, PuzzleView
from saulve.puzzle import Puzzle

from .base import Challenge as BaseChallenge
from .base import ChallengeLoader, PuzzleView

logger = logging.getLogger(__name__)

Expand All @@ -37,8 +38,8 @@ def get(self, *args: str) -> Puzzle:

try:
return self.puzzles[puzzle_id]
except KeyError:
raise PuzzleNotFound(f"Puzzle '{puzzle_id}' not found.")
except KeyError as e:
raise PuzzleNotFound(f"Puzzle '{puzzle_id}' not found.") from e


class GenericLoader(ChallengeLoader):
Expand All @@ -50,11 +51,14 @@ def __init__(
"""
Arguments:
challenge_module: The module to load puzzle from
id_regexp: An optional regexp to use to extract an identifier from loaded puzzle name.
If not set or if the regexp does not match, the puzzle module name will set as id.
id_regexp: An optional regexp to use to extract an identifier from
loaded puzzle name. If not set or if the regexp does not match,
the puzzle module name will set as id.
"""
self.challenge_module = challenge_module
self.id_regexp = re.compile(id_regexp) if id_regexp is not None else None
self.id_regexp = (
re.compile(id_regexp) if id_regexp is not None else None
)

def _generate_puzzle_id(self, puzzle_module_name: str) -> str:
puzzle_module = puzzle_module_name.split('.')[-1]
Expand Down Expand Up @@ -95,7 +99,10 @@ def load(self) -> Challenge:

puzzle_id = self._generate_puzzle_id(puzzle_module_name)
if puzzle_id in puzzles:
logger.warning(f'Duplicated puzzle id \'{puzzle_id}\' in {puzzle_module_name}')
logger.warning(
f'Duplicated puzzle id \'{puzzle_id}\' in '
f'{puzzle_module_name}'
)
puzzles[puzzle_id] = puzzle

return Challenge(puzzles)
1 change: 1 addition & 0 deletions saulve/challenges/in_memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""

from saulve.puzzle import Puzzle

from .base import Challenge, ChallengeLoader
from .generic import Challenge as GenericChallenge

Expand Down
6 changes: 3 additions & 3 deletions saulve/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ def solve(ctx: click.Context, puzzle_id: list[str]) -> None:
try:
puzzle = challenge.get(*puzzle_id)
except ValidationError as e:
raise click.ClickException(f'Invalid puzzle id. {e}')
except PuzzleNotFound:
raise click.ClickException('Puzzle not found.')
raise click.ClickException(f'Invalid puzzle id. {e}') from e
except PuzzleNotFound as e:
raise click.ClickException('Puzzle not found.') from e

solutions = puzzle.solve()

Expand Down
8 changes: 3 additions & 5 deletions saulve/import_module.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
"""Utility functions for dynamic imports
"""

from importlib import import_module
import importlib.util
from importlib import import_module
from pathlib import Path
from typing import Type, TypeVar

from saulve.errors import MissingAttribute, WrongAttributeType


T = TypeVar('T')


Expand All @@ -29,16 +28,15 @@ def import_instance(module_name: str, attr: str, cls: Type[T]) -> T:

try:
instance = getattr(module, attr)
except AttributeError:
raise MissingAttribute(module_name, attr)
except AttributeError as e:
raise MissingAttribute(module_name, attr) from e

if not isinstance(instance, cls):
raise WrongAttributeType(f'{module_name}.{attr}', cls)

return instance



def append_module_path(module_name: str, sys_path: list[str]) -> None:
"""Given a module name, if the module is not directly importable tries
to find if this module exists in the current working directory.
Expand Down
8 changes: 6 additions & 2 deletions saulve/puzzle.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

from .errors import PuzzleHasNoSolution


__all__ = ['Puzzle']


Expand Down Expand Up @@ -43,7 +42,12 @@ class Puzzle(Generic[PuzzleInput]):
puzzle_input: Input problem to solve
steps: A list of callable implementing puzzle solutions
"""
def __init__(self, name: str, puzzle_input: PuzzleInput|None = None) -> None:

def __init__(
self,
name: str,
puzzle_input: PuzzleInput | None = None,
) -> None:
self.name = name
self.puzzle_input = puzzle_input
self.steps: list[SolutionFunction] = []
Expand Down
1 change: 0 additions & 1 deletion tests/challenges/fixtures/generic/contains_puzzle.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from saulve import Puzzle


puzzle = Puzzle(name='A puzzle', puzzle_input=12)
2 changes: 1 addition & 1 deletion tests/challenges/test_advent_of_code.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from unittest.mock import Mock

from saulve.puzzle import Puzzle
from saulve.challenges.advent_of_code import AdventOfCodePuzzle, Calendar
from saulve.puzzle import Puzzle


class TestCalendar:
Expand Down
8 changes: 6 additions & 2 deletions tests/challenges/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,16 @@ def test_extract_puzzle_id_from_regexp(self) -> None:
def test_warn_about_duplicated_puzzle_id(self, caplog) -> None:
loader = GenericLoader(generic_fixtures, id_regexp=r'(puzzle)')

with caplog.at_level(logging.WARNING, logger='saulve.challenge.generic'):
with caplog.at_level(
logging.WARNING,
logger='saulve.challenge.generic'
):
loader.load()

assert (
re.search(
r"Duplicated puzzle id 'puzzle' in tests.challenges.fixtures.generic.\w*puzzle\w*",
r"Duplicated puzzle id 'puzzle' in "
r"tests.challenges.fixtures.generic.\w*puzzle\w*",
caplog.text,
) is not None
), f'"{caplog.text}" does not match expected message.'
1 change: 0 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from saulve.challenges.in_memory import InMemoryLoader
from saulve.cli import cli


puzzle = Puzzle(name='Test puzzle', puzzle_input='foo')
puzzle.solution(lambda a: 'bar')

Expand Down

0 comments on commit 2cd23f7

Please sign in to comment.