Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bbf2b9c
Added pylint examples of bad code for linting rules.
BethanyG Feb 17, 2026
a2e4973
Added pylint examples of good code for linting rules.
BethanyG Feb 17, 2026
87d9a79
Added pylint linting rules details files.
BethanyG Feb 17, 2026
8d0e8d9
Added generic pylint analyzer for exercises without any customized an…
BethanyG Feb 17, 2026
9f91a50
Added pylint linting rules related info files.
BethanyG Feb 17, 2026
3f8f33f
Custom analyzer stub, ready for additional rules beyond the existing …
BethanyG Feb 17, 2026
29393e1
Details comment file for unused-wildcard-import lint rule.
BethanyG Feb 17, 2026
6aa0151
Added exercise metadata and exemplar code to test exercises.
BethanyG Feb 17, 2026
e084adf
Added exercise solution code to test exercises.
BethanyG Feb 17, 2026
884c6fc
Added exercise golden analysis.json for test exercises.
BethanyG Feb 17, 2026
08d4647
Upgraded dockerfile to python:3.13.5-alpine3.22
BethanyG Feb 17, 2026
0c5d664
Required init files for test and lib.
BethanyG Feb 17, 2026
70d0535
Updated readme with new docker-based instructions.
BethanyG Feb 17, 2026
13b7c2e
Updated PyLint comment files with good code, bad code, refs, and deta…
BethanyG Feb 17, 2026
fac5dbc
Removed unneeded pylint config file from exercise.
BethanyG Feb 17, 2026
0457dac
Added 10 general recommendations for when the analyzer has no feedback.
BethanyG Feb 17, 2026
e7b4bd6
Cleaned up and formatted run scripts.
BethanyG Feb 17, 2026
020f6e8
Upgraded requirements for Python 3.13.5.
BethanyG Feb 17, 2026
f27da51
Upgraded the general PyLint config file with the 4.4.0 version and en…
BethanyG Feb 17, 2026
c5d9ec0
Added new golden tests for slect exercises to better test Analyzer an…
BethanyG Feb 17, 2026
d0a9be9
Modified analyzer to use new method now that epylint is depricated. …
BethanyG Feb 17, 2026
85753d3
Added exercise names file and modified exercise.py to read exercise n…
BethanyG Feb 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
17 changes: 7 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
FROM python:3.11.2-slim
FROM python:3.13.5-alpine3.22

RUN apt-get update \
&& apt-get install curl -y \
&& apt-get remove curl -y \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt /requirements.txt
COPY dev-requirements.txt /dev-requirements.txt

RUN pip install -r /requirements.txt -r /dev-requirements.txt

RUN mkdir /opt/analyzer
COPY . /opt/analyzer
WORKDIR /opt/analyzer

RUN pip install -r requirements.txt -r dev-requirements.txt
ENTRYPOINT ["/opt/analyzer/bin/run.sh"]
WORKDIR /opt/analyzer

ENTRYPOINT ["sh", "/opt/analyzer/bin/run.sh"]
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
# Exercism's Python Analyzer

This is Exercism's automated analyzer for the Python track.
This is Exercism's automated analyzer for the Python track exercises.
It is based on and uses [PyLint][pylint-github].

It is run with `./bin/run.sh $EXERCISM $PATH_TO_FILES $PATH_FOR_OUTPUT` and will read the source code from `$PATH_TO_FILES` and write a text file with an analysis to `$PATH_FOR_OUTPUT`.
It is run from a docker container using `./bin/run-in-docker.sh $EXERCISM $PATH_TO_FILES $PATH_FOR_OUTPUT` and will read the source code from `$PATH_TO_FILES` and write a text file with an analysis to `$PATH_FOR_OUTPUT`.

For example:

```bash
./bin/run.sh two_fer ~/solution-238382y7sds7fsadfasj23j/ ~/solution-238382y7sds7fsadfasj23j/output/
./bin/run-in-docker.sh two_fer ~/solution-238382y7sds7fsadfasj23j/ ~/solution-238382y7sds7fsadfasj23j/output/
```

Unit tests can be run from this directory:
Unit tests also require [docker][docker] and can be run locally or from within GitHub via [codespaces][codespaces]:

```bash
pytest -x

#run from the python-analyzer (project root) directory.
./bin/run-tests-in-docker.sh
```

[pylint-github]: https://github.com/pylint-dev/pylint
[docker]: https://www.docker.com/
[codespaces]: https://github.com/features/codespaces
8 changes: 5 additions & 3 deletions bin/run-in-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ mkdir -p "$output_dir"

# run image passing the arguments
docker run \
--rm \
--network none \
--read-only \
--mount type=bind,src=$PWD/$2,dst=/solution \
--mount type=bind,src=$PWD/$output_dir,dst=/output \
python-analyzer $1 /solution/ /output/


--mount type=tmpfs,destination=/tmp \
exercism/python-analyzer $1 /solution/ /output/
30 changes: 30 additions & 0 deletions bin/run-tests-in-docker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env sh

# Synopsis:
# Test the test runner Docker image by running it against a predefined set of
# solutions with an expected output.
# The test runner Docker image is built automatically.

# Output:
# Outputs the diff of the expected test results against the actual test results
# generated by the test runner Docker image.

# Example:
# ./bin/run-tests-in-docker.sh

# Stop executing when a command returns a non-zero return code
set -e

# Build the Docker image
docker build --rm -t exercism/python-analyzer .

# Run the Docker image using the settings mimicking the production environment
docker run \
--rm \
--network none \
--read-only \
--mount type=bind,src="${PWD}/test",dst=/opt/analyzer/test \
--mount type=tmpfs,dst=/tmp \
--workdir /opt/analyzer \
--entrypoint pytest \
exercism/python-analyzer -vv --disable-warnings
2 changes: 2 additions & 0 deletions bin/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
CLI for the auto-analyzer for the Python track on Exercism.org.
./bin/run.sh two_fer ~/solution-238382y7sds7fsadfasj23j/ ~/solution-238382y7sds7fsadfasj23j/output/
"""


import argparse
import importlib.util
import sys
Expand Down
32 changes: 32 additions & 0 deletions comments/general/general_recommendations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
1. Get familiar with the conventions outlined in [PEP 8][pep-8].
While these are not "law", they are the standard used in the Python project itself and are a great baseline in most coding situations.
2. Read and think about the ideas outlined in [PEP 20 (aka "The Zen of Python")][pep-20].
Like PEP 8, these are not "laws", but they are solid guiding principles for better and clearer Python code.
3. Prefer clear and easy to follow code over comments. But DO comment where needed for clarity.
4. Consider using type hints to clarify your code.
Explore the type hint [documentation][type-hint-docs] and [why you might not want to type hint][type-hint-nos].
5. Try to follow the docstring guidelines laid out in [PEP 257][pep-257].
Good documentation matters.
6. Avoid [magic numbers][magic-numbers].
7. Prefer [`enumerate()`][enumerate-docs] over [`range(len())`][range-docs] in loops that need both an index and element.
8. Prefer [comprehensions][comprehensions] and [generator expressions][generators] over loops that append to a data structure.
But don't [overuse comprehensions][comprehension-overuse].
9. When joining more than few substrings or concatenating in a loop, prefer [`str.join()`][join] over other methods of string concatenation.
10. Get familiar with Python's rich set of [built-in functions][built-in-functions] and the [Standard Library][standard-lib].
Go [here][standard-lib-overview] for a brief tour and some interesting highlights.

[built-in-functions]: https://docs.python.org/3/library/functions.html
[comprehension-overuse]: https://treyhunner.com/2019/03/abusing-and-overusing-list-comprehensions-in-python/
[comprehensions]: https://treyhunner.com/2015/12/python-list-comprehensions-now-in-color/
[enumerate-docs]: https://docs.python.org/3/library/functions.html#enumerate
[generators]: https://www.pythonmorsels.com/how-write-generator-expression/
[join]: https://docs.python.org/3/library/stdtypes.html#str.join
[magic-numbers]: https://en.wikipedia.org/wiki/Magic_number_(programming)
[pep-20]: https://peps.python.org/pep-0020/
[pep-257]: https://peps.python.org/pep-0257/
[pep-8]: https://peps.python.org/pep-0008/
[range-docs]: https://docs.python.org/3/library/functions.html#func-range
[standard-lib-overview]: https://docs.python.org/3/tutorial/stdlib.html
[standard-lib]: https://docs.python.org/3/library/index.html
[type-hint-docs]: https://typing.python.org/en/latest/index.html
[type-hint-nos]: https://typing.python.org/en/latest/guides/typing_anti_pitch.html
14 changes: 10 additions & 4 deletions comments/pylint/convention.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# pylint convention

**Line %{lineno}** [ _%{code}_ ] : %{message}.
Was reported.
**Line %{lineno} [_%{code}_]** was reported by Pylint:

Which means this code doesn't follow general [`code style`][PEP8] conventions.
%{message}.

This code doesn't follow general [Python code style][code style] conventions.
While this type of issue generally doesn't affect the way code _executes_, it can hurt readability or the performance of automated tools such as documentation generators or test runners.

[PEP8]: https://www.python.org/dev/peps/pep-0008/
%{bad_code}
%{good_code}
%{related_info}
%{details}

[code style]: https://www.python.org/dev/peps/pep-0008/
10 changes: 8 additions & 2 deletions comments/pylint/error.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
# pylint informational

**Line %{lineno}** [ _%{code}_ ] : %{message}.
Was reported.
**Line %{lineno} [_%{code}_]** was reported by Pylint:

%{message}.

This code has an error or problem that should be addressed.

%{bad_code}
%{good_code}
%{related_info}
%{details}
8 changes: 4 additions & 4 deletions comments/pylint/fatal.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# pylint fatal

**Line %{lineno}** [ _%{code}_ ] : %{message}.
Was reported.
**Line %{lineno} [_%{code}_]** was reported by Pylint:

This is a fatal error.
Something went wrong, and the code cannot be processed any further.
%{message}.


This is a fatal error. Something went wrong, and the code cannot be processed any further.
11 changes: 8 additions & 3 deletions comments/pylint/informatonal.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# pylint informational

**Line %{lineno}** [ _%{code}_ ] : %{message}.
Was reported.
**Line %{lineno} [_%{code}_]** was reported by Pylint:

%{message}.

There is `FIXME`/`TODO`/`XXX` style comment or other "informational" pattern or note in the code.
These tags are often used to annotate places where code is stubbed out but needs work - or to highlight potential design flaws or bugs that need to be addressed in the future.
These tags are often used to annotate places where code is stubbed out but needs work - or to highlight potential design flaws or bugs that need to be addressed in the future.

%{bad_code}
%{good_code}
%{related_info}
%{details}
16 changes: 12 additions & 4 deletions comments/pylint/refactor.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
# pylint refactor

**Line %{lineno}** [ _%{code}_ ] : %{message}.
Was reported.
**Line %{lineno} [_%{code}_]** was reported by Pylint:

This code is emitting a [`code smell`][code smell], and may be in need of a rewrite or refactor.
This doesn't mean the code is incorrect or buggy in a _technical sense_ -- only that it appears to have one or more patterns that could lead to _future_ bugs or maintenance issues.
%{message}.

This code is emitting a [code smell][code smell], and may be in need of
a re-write or refactor.
This doesn't mean the code is incorrect or buggy in a _technical sense_, only that it
appears to have one or more patterns that could lead to _future_ bugs or maintenance issues.
Consider taking a closer look at it.

%{bad_code}
%{good_code}
%{related_info}
%{details}

[code smell]: https://en.wikipedia.org/wiki/Code_smell
11 changes: 8 additions & 3 deletions comments/pylint/warning.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# pylint warning

**Line %{lineno}** [ _%{code}_ ] : %{message}.
Was reported.
**Line %{lineno} [_%{code}_]** was reported by Pylint:

%{message}.

There is an issue in the code that could lead to a bug or error in the program.
While this error might not be _severe_, it could lead to more severe issues in the future.
It is recommend the problem be addressed before proceeding further.
It is recommended the problem be addressed before proceeding further.

%{bad_code}
%{good_code}
%{related_info}
%{details}
6 changes: 3 additions & 3 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pytest~=7.2.2
pytest-subtests~=0.10.0
tomli>=1.1.0; python_full_version < '3.11.2'
black<=25.1.0
pytest~=8.4.0
pytest-subtests~=0.14.2
Empty file added lib/__init__.py
Empty file.
19 changes: 15 additions & 4 deletions lib/black-jack/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
"""

import ast

from io import StringIO
from pylint.lint import Run
from pylint import run_pylint
from pathlib import Path
from pylint import epylint as lint
from pylint.reporters.text import TextReporter

from common import Analysis, BaseFeedback, Summary
from common.comment import Comment, CommentTypes
Expand All @@ -15,13 +19,16 @@
class Comments(BaseFeedback):
NO_MODULE = ("general", "no_module")
NO_METHOD = ("two-fer", "no_method")
NO_RETURN = ("general", "no_return")
MALFORMED_CODE = ("general", "malformed_code")
GENERAL_RECS = ("general", "general_recommendations")


def analyze(in_path: Path, out_path: Path):
"""Analyze the user's Black Jack solution and give feedback.
"""
Analyze the user's Two Fer solution to give feedback. Outputs a JSON that

Outputs a JSON that conforms to:
https://github.com/exercism/docs/blob/main/building/tooling/analyzers/interface.md#output-format
conforms to https://github.com/exercism/docs/blob/main/building/tooling/analyzers/interface.md#output-format
"""

# List of Comment objects to process
Expand Down Expand Up @@ -52,4 +59,8 @@ def analyze(in_path: Path, out_path: Path):
# Generate PyLint comments for additional feedback.
comments.extend(generate_pylint_comments(in_path))

# If there are no comments, add the general recommendations as comments.
if not comments:
comments.append(Comment(type=CommentTypes.INFORMATIVE, params={}, comment=Comments.GENERAL_RECS))

return Analysis.summarize_comments(comments, output_file)
20 changes: 16 additions & 4 deletions lib/card-games/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
"""
Basic analyzer for the `card-games` exercise.
Only Pylint checks are active currently.
Only Pylint checks are currently active.
"""

import ast
from pylint import epylint as lint

from io import StringIO
from pylint.lint import Run
from pylint import run_pylint
from pathlib import Path
from pylint.reporters.text import TextReporter

from common import Analysis, BaseFeedback, Summary
from common.comment import Comment, CommentTypes
from common.pylint_comments import generate_pylint_comments


class Comments(BaseFeedback):
NO_MODULE = ("general", "no_module")
NO_METHOD = ("two-fer", "no_method")
NO_RETURN = ("general", "no_return")
MALFORMED_CODE = ("general", "malformed_code")
GENERAL_RECS = ("general", "general_recommendations")


def analyze(in_path: Path, out_path: Path):
"""
Analyze the user's Two Fer solution to give feedback. Outputs JSON that
Analyze the user's Two Fer solution to give feedback. Outputs a JSON that

conforms to https://github.com/exercism/docs/blob/main/building/tooling/analyzers/interface.md#output-format
"""
Expand Down Expand Up @@ -47,8 +56,11 @@ def analyze(in_path: Path, out_path: Path):
if comments:
return Analysis.require(comments).dump(output_file)


# Generate PyLint comments for additional feedback.
comments.extend(generate_pylint_comments(in_path))

# If there are no comments, add the general recommendations as comments.
if not comments:
comments.append(Comment(type=CommentTypes.INFORMATIVE, params={}, comment=Comments.GENERAL_RECS))

return Analysis.summarize_comments(comments, output_file)
21 changes: 18 additions & 3 deletions lib/cater-waiter/analyzer.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
"""
Analyzer for the `cater-waiter` exercise.
Only has Pylint check active currently.
Generic Analyzer for the exercises that don't have a specific
analyzer or specific customizations.

Only Pylint comments are active.
"""

import ast
from pylint import epylint as lint

from io import StringIO
from pylint.lint import Run
from pylint import run_pylint
from pathlib import Path
from pylint.reporters.text import TextReporter

from common import Analysis, BaseFeedback, Summary
from common.comment import Comment, CommentTypes
from common.pylint_comments import generate_pylint_comments


class Comments(BaseFeedback):
NO_MODULE = ("general", "no_module")
NO_METHOD = ("two-fer", "no_method")
NO_RETURN = ("general", "no_return")
MALFORMED_CODE = ("general", "malformed_code")
GENERAL_RECS = ("general", "general_recommendations")


def analyze(in_path: Path, out_path: Path):
"""
Expand Down Expand Up @@ -50,4 +61,8 @@ def analyze(in_path: Path, out_path: Path):
# Generate PyLint comments for additional feedback.
comments.extend(generate_pylint_comments(in_path))

# If there are no comments, add the general recommendations as comments.
if not comments:
comments.append(Comment(type=CommentTypes.INFORMATIVE, params={}, comment=Comments.GENERAL_RECS))

return Analysis.summarize_comments(comments, output_file)
Loading