From 00415e660a5272a3e31b56e8d0f8a973d3a63abb Mon Sep 17 00:00:00 2001 From: Inga Ulusoy Date: Tue, 28 May 2024 08:23:19 +0200 Subject: [PATCH] Initial commit --- .github/workflows/Part1/example1.py | 36 +++++++ .github/workflows/Part1/example2.py | 22 ++++ .github/workflows/Part1/example3.py | 29 +++++ .github/workflows/ci.yml | 35 ++++++ .github/workflows/classroom.yml | 27 +++++ .gitignore | 160 ++++++++++++++++++++++++++++ LICENSE | 21 ++++ README.md | 2 + chapter1/example1.py | 34 ++++++ chapter1/example2.py | 19 ++++ chapter1/example3.py | 25 +++++ requirements.txt | 3 + tests/test_fixmes.py | 54 ++++++++++ 13 files changed, 467 insertions(+) create mode 100644 .github/workflows/Part1/example1.py create mode 100644 .github/workflows/Part1/example2.py create mode 100644 .github/workflows/Part1/example3.py create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/classroom.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 chapter1/example1.py create mode 100644 chapter1/example2.py create mode 100644 chapter1/example3.py create mode 100644 requirements.txt create mode 100644 tests/test_fixmes.py diff --git a/.github/workflows/Part1/example1.py b/.github/workflows/Part1/example1.py new file mode 100644 index 0000000..f3ee014 --- /dev/null +++ b/.github/workflows/Part1/example1.py @@ -0,0 +1,36 @@ +import os +import glob + + +# find all png files in a folder +def find_files(path=None, pattern="*.png", recursive=True, limit=20) -> list: + """Find image files on the file system. + + :param path: + The base directory where we are looking for the images. + Defaults to None, which uses the XDG data directory if + set or the current working directory otherwise. + :param pattern: + The naming pattern that the filename should match. Defaults to + "*.png". Can be used to allow other patterns or to only include + specific prefixes or suffixes. + :param recursive: + Whether to recurse into subdirectories. + :param limit: + The maximum number of images to be found. Defaults to 20. + To return all images, set to None. + """ + if path is None: + path = os.environ.get("XDG_DATA_HOME", ".") + + result = list(glob.glob(f"{path}/{pattern}", recursive=recursive)) + + if limit is not None: + result = result[:limit] + + return result + + +if __name__ == "__main__": + list = find_files(path="./data/") + print("Found files {}".format(list)) diff --git a/.github/workflows/Part1/example2.py b/.github/workflows/Part1/example2.py new file mode 100644 index 0000000..27778e9 --- /dev/null +++ b/.github/workflows/Part1/example2.py @@ -0,0 +1,22 @@ +import numpy as np + + +def area_circ(r_in): + """Calculate the area of a circle with given radius. + + :Input: The radius of the circle (float, >=0). + :Returns: The area of the circle (float).""" + if r_in < 0: + raise ValueError("The radius must be >= 0.") + circle = np.pi * r_in**2 + print( + """The area of a circle with radius r = {:3.2f}cm is \ + A = {:4.2f}cm2.""".format( + r_in, circle + ) + ) + return circle + + +if __name__ == "__main__": + _ = area_circ(5.0) diff --git a/.github/workflows/Part1/example3.py b/.github/workflows/Part1/example3.py new file mode 100644 index 0000000..22115bf --- /dev/null +++ b/.github/workflows/Part1/example3.py @@ -0,0 +1,29 @@ +def validate_data_dict(data_dict): + if not data_dict: + raise ValueError("data_dict is empty") + for something, otherthing in data_dict.items(): + if not otherthing: + raise ValueError(f"The dict content under {something} is empty.") + if not isinstance(otherthing, dict): + raise ValueError( + f"The content of {something} is \ + not a dict but {type(otherthing)}." + ) + + list_ = ["data", "file_type", "sofa", "paragraph"] + missing_cats = [] + for category in list_: + if category not in list(otherthing.keys()): + missing_cats.append(category) + + if missing_cats: + raise ValueError( + f"Data dict is missing categories: \ + {missing_cats}" + ) + + +if __name__ == "__main__": + data_dict = {} + data_dict = {"test": {"testing": "just testing"}} + validate_data_dict(data_dict) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..386e070 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,35 @@ +name: test +# Controls when the action will run. +on: + # Triggers the workflow on push or pull request events but only for the main branch + push: + branches: [ main ] + pull_request: + branches: [ main ] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + test-input: + # The type of runner that the job will run on + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + python-version: ["3.12"] + steps: + - name: Check out repository + uses: actions/checkout@v4 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Set up environment + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Run tests + run: | + python -m pytest -sxvv \ No newline at end of file diff --git a/.github/workflows/classroom.yml b/.github/workflows/classroom.yml new file mode 100644 index 0000000..b085796 --- /dev/null +++ b/.github/workflows/classroom.yml @@ -0,0 +1,27 @@ +name: Autograding Tests +'on': +- push +- repository_dispatch +permissions: + checks: write + actions: read + contents: read +jobs: + run-autograding-tests: + runs-on: ubuntu-latest + if: github.actor != 'github-classroom[bot]' + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: check-for-stylistic-errors + id: check-for-stylistic-errors + uses: classroom-resources/autograding-python-grader@v1 + with: + timeout: 10 + setup-command: 'pip install flake8' + - name: Autograding Reporter + uses: classroom-resources/autograding-grading-reporter@v1 + env: + CHECK-FOR-STYLISTIC-ERRORS_RESULTS: "${{steps.check-for-stylistic-errors.outputs.result}}" + with: + runners: check-for-stylistic-errors diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3fb950e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 ssciwr-courses + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..aba473a --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# pbp-pep-fixmes +Correct the three example files in chapter 1 to adhere to PEP 8 recommendations. diff --git a/chapter1/example1.py b/chapter1/example1.py new file mode 100644 index 0000000..d32806b --- /dev/null +++ b/chapter1/example1.py @@ -0,0 +1,34 @@ +import os +import glob + + + +# find all png files in a folder +def find_files(path=None, pattern="*.png", recursive=True, limit = 20) -> list: + """Find image files on the file system. + + :param path: + The base directory where we are looking for the images. Defaults to None, which uses the XDG data directory if set or the current working directory otherwise. + :param pattern: + The naming pattern that the filename should match. Defaults to + "*.png". Can be used to allow other patterns or to only include + specific prefixes or suffixes. + :param recursive: + Whether to recurse into subdirectories. + :param limit: + The maximum number of images to be found. Defaults to 20. + To return all images, set to None. + """ + if path is None: + path = os.environ.get("XDG_DATA_HOME", ".") + + result=list(glob.glob(f"{path}/{pattern}", recursive=recursive)) + + if limit is not None: + result = result[:limit] + + return result + +if __name__=="__main__": + list = find_files(path="./data/") + print("Found files {}".format(list)) \ No newline at end of file diff --git a/chapter1/example2.py b/chapter1/example2.py new file mode 100644 index 0000000..da93dd1 --- /dev/null +++ b/chapter1/example2.py @@ -0,0 +1,19 @@ +import numpy as np + +def area_circ(r_in ): + """Calculate the area of a circle with given radius. + + :Input: The radius of the circle (float, >=0). + :Returns: The area of the circle (float).""" + if r_in<0: + raise ValueError("The radius must be >= 0.") + Kreis=np.pi*r_in**2 + print( + """The area of a circle with radius r = {:3.2f}cm is A = {:4.2f}cm2.""".format( + r_in,Kreis + ) + ) + return Kreis + +if __name__ == "__main__": + _ = area_circ(5.0) \ No newline at end of file diff --git a/chapter1/example3.py b/chapter1/example3.py new file mode 100644 index 0000000..86bb53b --- /dev/null +++ b/chapter1/example3.py @@ -0,0 +1,25 @@ +def validate_data_dict(data_dict): + if not data_dict: + raise ValueError("data_dict is empty") + for something, otherthing in data_dict.items(): + if not otherthing: + raise ValueError(f"The dict content under {something} is empty.") + if not isinstance(otherthing, dict): + raise ValueError( + f"The content of {something} is not a dict but {type(otherthing)}." + ) + + list = ["data", "file_type", "sofa", "paragraph"] + missing_cats = [] + for category in list: + if category not in list(otherthing.keys()): + missing_cats.append(category) + + if missing_cats: + raise ValueError(f"Data dict is missing categories: {missing_cats}") + + +if __name__ == "__main__": + data_dict = {} + data_dict = {"test": {"testing": "just testing"}} + validate_data_dict(data_dict) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..fe3757d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +pytest +black +flake8 \ No newline at end of file diff --git a/tests/test_fixmes.py b/tests/test_fixmes.py new file mode 100644 index 0000000..39ac7f8 --- /dev/null +++ b/tests/test_fixmes.py @@ -0,0 +1,54 @@ +import os +import re +import subprocess +from pathlib import Path + + +def test_flake8(): + # Get the repository directory + current_dir = Path(__file__).resolve().parents[1] + input_file_path = current_dir / "chapter1" + # run flake8 on the example files + command = "flake8 {}".format(input_file_path) + failure = 0 + try: + subprocess.check_output(command, shell=True) + except subprocess.CalledProcessError as e: + failure = e.returncode + # if there are some, print the differences and calculate no of errors + if failure == 1: + os.system("flake8 {}".format(input_file_path)) + print("Please try again!") + else: + print("No stylistic errors found!") + assert failure == 0 + + +def test_german_name(): + # Kreis in example 2 + current_dir = Path(__file__).resolve().parents[1] + input_file = current_dir / "chapter1" / "example2.py" + # figure out if the word "Kreis" is in the file + with open(input_file, "r") as f: + file_content = f.read() + assert "Kreis" not in file_content + + +def test_intrinsic_function(): + # check variable name in example3 + current_dir = Path(__file__).resolve().parents[1] + input_file = current_dir / "chapter1" / "example3.py" + # make sure the intrinsic "list" function is not used as a variable name + with open(input_file, "r") as f: + file_content = f.read() + # find all appearances of "list" in the file + find_list = [i.start() for i in re.finditer("list", file_content)] + not_accepted_characters = [" ", ":"] + failure = 0 + for i in find_list: + check_last_character = file_content[i+4] + if check_last_character in not_accepted_characters: + print("Found 'list' as variable name in the file! Please change the variable name.") + print(file_content[i:i+5]) + failure = 1 + assert failure == 0