From 4b2155d84f849d92b1a1d0cab9db867f3b36ee8f Mon Sep 17 00:00:00 2001 From: Hassan Abedi Date: Sat, 19 Oct 2024 11:41:14 +0200 Subject: [PATCH] Initial commit --- .coveragerc | 16 ++ .editorconfig | 29 ++++ .gitattributes | 64 +++++++ .github/workflows/publish_to_pypi.yml | 40 +++++ .github/workflows/tests.yml | 43 +++++ .gitignore | 75 ++++++++ LICENSE | 21 +++ README.md | 126 ++++++++++++++ assets/dummy_figure.dot | 75 ++++++++ assets/dummy_figure.svg | 239 ++++++++++++++++++++++++++ assets/logo.dot | 10 ++ assets/logo.svg | 19 ++ assets/make_figures.sh | 9 + docs/index.md | 12 ++ pyproject.toml | 98 +++++++++++ src/__init__.py | 0 src/package/__init__.py | 0 src/package/module.py | 17 ++ tests/__init__.py | 0 tests/test_module.py | 16 ++ 20 files changed, 909 insertions(+) create mode 100644 .coveragerc create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/publish_to_pypi.yml create mode 100644 .github/workflows/tests.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 assets/dummy_figure.dot create mode 100644 assets/dummy_figure.svg create mode 100644 assets/logo.dot create mode 100644 assets/logo.svg create mode 100644 assets/make_figures.sh create mode 100644 docs/index.md create mode 100644 pyproject.toml create mode 100644 src/__init__.py create mode 100644 src/package/__init__.py create mode 100644 src/package/module.py create mode 100644 tests/__init__.py create mode 100644 tests/test_module.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..7620c97 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,16 @@ +[run] +# Exclude test files and specific init files from the coverage report +omit = + */tests/* + */test_*.py + */__init__.py # Good idea to exclude __init__.py files from the coverage report + +# Include source files only from certain directories +source = + src/ + +# Set parallel to true if you run tests in parallel +parallel = True + +# Enable branch coverage if set to True +branch = False diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a603d26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# Top-most EditorConfig file +root = true + +# Global settings (applicable to all files unless overridden) +[*] +charset = utf-8 # Default character encoding +end_of_line = lf # Use LF for line endings (Unix-style) +indent_style = space # Use spaces for indentation +indent_size = 4 # Default indentation size +insert_final_newline = true # Make sure files end with a newline +trim_trailing_whitespace = true # Remove trailing whitespace + +# Python specific settings, complying with PEP 8 style guide, except for the line length +[*.py] +max_line_length = 100 # Limit lines to 100 characters + +# Markdown files +[*.md] +trim_trailing_whitespace = false # Don't remove trailing whitespace in Markdown files + +# Bash scripts +[*.sh] +indent_size = 2 + +# YAML files +[*.yml] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ae6a95 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,64 @@ +# Common document and text file formats +*.docx filter=lfs diff=lfs merge=lfs -text +*.doc filter=lfs diff=lfs merge=lfs -text +*.pdf filter=lfs diff=lfs merge=lfs -text +*.xls filter=lfs diff=lfs merge=lfs -text +*.xlsx filter=lfs diff=lfs merge=lfs -text +*.ppt filter=lfs diff=lfs merge=lfs -text +*.pptx filter=lfs diff=lfs merge=lfs -text + +# Common image formats +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.gif filter=lfs diff=lfs merge=lfs -text +*.bmp filter=lfs diff=lfs merge=lfs -text +*.tiff filter=lfs diff=lfs merge=lfs -text +*.tif filter=lfs diff=lfs merge=lfs -text + +# Common compressed file formats +*.zip filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.7z filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text + +# Common file formats in machine learning projects +*.bin filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.tfrecord filter=lfs diff=lfs merge=lfs -text +*.hdf5 filter=lfs diff=lfs merge=lfs -text +*.keras filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.pkl filter=lfs diff=lfs merge=lfs -text +*.pickle filter=lfs diff=lfs merge=lfs -text +*.npy filter=lfs diff=lfs merge=lfs -text + +# Common audio and video formats +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.avi filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +*.flac filter=lfs diff=lfs merge=lfs -text +*.mkv filter=lfs diff=lfs merge=lfs -text +*.webm filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.ogv filter=lfs diff=lfs merge=lfs -text + +# Common data transfer formats +#*.csv filter=lfs diff=lfs merge=lfs -text +#*.tsv filter=lfs diff=lfs merge=lfs -text +#*.json filter=lfs diff=lfs merge=lfs -text +#*.xml filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.feather filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.avro filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.orc filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml new file mode 100644 index 0000000..d41b39d --- /dev/null +++ b/.github/workflows/publish_to_pypi.yml @@ -0,0 +1,40 @@ +name: Publish to PyPI + +on: + workflow_dispatch: # Enable manual runs + +jobs: + + # Run tests before publishing + call_tests: + uses: ./.github/workflows/tests.yml + + publish_to_pypi: + runs-on: ubuntu-latest + needs: call_tests + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set Up Python + uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - name: Install Poetry + run: | + pip install poetry + + - name: Install Dependencies + run: | + poetry install + + # - name: Update Version + # run: | + # poetry version patch # Use 'minor' or 'major' for minor or major version bumps + + - name: Build and Publish Package + run: | + poetry config pypi-token.pypi ${{ secrets.PYPI_API_TOKEN }} # Make sure you've set `secrets.PYPI_API_TOKEN` + poetry publish --build diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..7f2001b --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,43 @@ +name: Tests + +on: + workflow_dispatch: # Only enable manual runs for now + workflow_call: # Make this workflow available to be called by other workflows + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + # Define the Python versions to test against + python-version: [ "3.10", "3.11", "3.12" ] + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Set Up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + # Install Poetry and Dependencies and run tests with coverage and upload test results and coverage reports as artifacts + - name: Install Poetry and Dependencies + run: | + pip install poetry + poetry install + + - name: Run Tests with Coverage + run: | + poetry run pytest tests/ --doctest-modules --cov=src --cov-report xml --cov-report html \ + --junitxml=junit/test-results-${{ matrix.python-version }}.xml + continue-on-error: false + + - name: Upload Test Results and Coverage Reports + uses: actions/upload-artifact@v4 + with: + name: test-results-and-coverage-${{ matrix.python-version }} + path: | + junit/test-results-${{ matrix.python-version }}.xml + htmlcov/ diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ee1138b --- /dev/null +++ b/.gitignore @@ -0,0 +1,75 @@ +# Python specific +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments +.env/ +env/ +.venv/ +venv/ + +# Packaging and distribution files +.Python +build/ +dist/ +*.egg-info/ +*.egg +MANIFEST + +# Dependency directories +develop-eggs/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +.installed.cfg + +# Test and coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# IDE specific files and directories +.idea/ +*.iml +.vscode/ + +# Jupyter Notebook files +.ipynb_checkpoints + +# Temporary files created by editors and the system and folders to ignore +*.swp +*~ +*.bak +*.tmp +temp/ +output/ +tmp/ +tmp2/ +out/ +out2/ + +# Database files (SQLite, DuckDB, etc.) +*.duckdb +*.db +*.wal +*.sqlite + +# Dependency lock files (uncomment to ignore) +poetry.lock + +# Miscellaneous files and directories to ignore +# Add any additional file patterns a directory names that should be ignored down here diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..feb81f7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Hassan Abedi + +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..ded0dc5 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# Template for a Python Library + + +[![Tests](https://github.com/habedi/template-python-library/actions/workflows/tests.yml/badge.svg)](https://github.com/habedi/template-python-library/actions/workflows/tests.yml) +[![PyPI version](https://badge.fury.io/py/template-python-library-placeholder.svg)](https://badge.fury.io/py/template-python-library-placeholder) +[![License](https://img.shields.io/github/license/habedi/template-python-library)](https://github.com/habedi/template-python-library/blob/main/LICENSE) +[![Python version](https://img.shields.io/badge/Python-%3E=3.10-blue)](https://github.com/habedi/template-python-library) +[![Pip downloads](https://img.shields.io/pypi/dm/template-python-library-placeholder.svg)](https://pypi.org/project/template-python-library-placeholder) +[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +[![CodeFactor](https://www.codefactor.io/repository/github/habedi/template-python-library/badge)](https://www.codefactor.io/repository/github/habedi/template-python-library) + +This is a template repository to make it easier to start developing Python libraries. The repository includes the basic +structure for a Python library, including the code for a dummy package, unit tests, and GitHub Actions workflows for +running tests and deploying the library to PyPI. + +As said above, I've created this template to make it easier to create new Python libraries. I hope you find it useful +as well. If you have any suggestions or improvements, feel free to open an issue or a pull request. + +## Getting Started + +To get started with this template, you can click on the `Use this template` button at the top of the repository. This +will create a new repository with the same structure as this one. You can then clone the repository to your local +machine and start developing your own Python library. + +### Installing Poetry + +We will use [Poetry](https://python-poetry.org/) to manage the dependencies and the Python virtual environment. To +get started, you need to install Poetry on your machine. We can install Poetry by running the following command in the +command line using pip. + +```bash +pip install poetry +``` + +When the installation is finished, run the following command in the shell in the root folder of this repository to +install the dependencies, and create a virtual environment we can use for development. + +```bash +poetry install +``` + +After that, enter the Poetry environment by invoking the poetry shell command. + +```bash +poetry shell +``` + +### Unit Tests and Code Coverage + +We use [pytest](https://docs.pytest.org/en/stable/) to run the tests. To run the tests with code coverage, you can +run the following command in the shell. + +```bash +poetry run pytest tests/ --cov +``` + +You can edit the entries under `[tool.pytest.ini_options]` in the `pyproject.toml` file to configure pytest behaviour. +You can also edit the `.coveragerc` file to configure the coverage report generation. + +### Building and Publishing + +As said before, we use Poetry to manage the dependencies and virtual environment. To build the pip package, you can +run +the following command in the shell. + +```bash +poetry build +``` + +This command will create a `dist` folder in the root directory, which contains the built version of the +library. To publish the library to PyPI, you can run the following command in the shell. + +```bash +poetry publish +``` + +Note that you need to have a PyPI account and a valid API token to publish the library on PyPI. You can find more +information +about +publishing pip packages to PyPI in the [Poetry documentation](https://python-poetry.org/docs/repositories/). + +### Files and Folders + +The main files and folders in this repository are: + +- `.github`: Contains the GitHub Actions workflows for running tests and deploying the built version of the library to + PyPI. +- `assets`: Contains the asset files for the library, like images, logos, etc. +- `docs`: Contains the documentation for the library. +- `src`: Includes the source code for the library. You can create multiple packages and modules in this folder. The + `src/package` folder contains a dummy package with a dummy class and method. +- `tests`: Contains the unit tests for the functions and methods in the library. +- `.editorconfig`: Configuration file for the code editor settings for consistent coding style. +- `.gitignore`: Python-specific gitignore file to exclude the files generated by Python and Poetry. +- `.gitattributes`: Configuration file for Git LFS. +- `LICENSE`: The license file for the library. +- `pyproject.toml`: The configuration file for Poetry, which manages the dependencies and virtual environment. + +### Example + +Here is an example of how you can use the code in the library after building and installing it as a pip package. + +```python +from src.package.module import DummyClass + +dummy = DummyClass() + +print(dummy.dummy_method()) +``` + +Before running the code, you need to install the library in the current environment. You can use Poetry to do this by +running the following commands in the shell. + +```bash +poetry build +poetry install +``` + +### Notes + +- To publish the library to PyPI, you need to have a PyPI account and a valid API token. You can find more + information about publishing pip packages to PyPI in + the [Poetry documentation](https://python-poetry.org/docs/repositories/). +- To run the `.github/workflows/publish_to_pypi.yml` workflow, you need to set the `PYPI_API_TOKEN` secret for + the repository. You can find more information about repository secrets in the + [GitHub documentation](https://docs.github.com/en/actions/reference/encrypted-secrets). diff --git a/assets/dummy_figure.dot b/assets/dummy_figure.dot new file mode 100644 index 0000000..9815a5e --- /dev/null +++ b/assets/dummy_figure.dot @@ -0,0 +1,75 @@ +digraph G { + node [fontname = "Arial", fontsize = 12]; + + // Title in the top left corner with margin + label = " Typical Structure of a Python Library"; + labelloc = "t"; + labeljust = "l"; + fontsize = 16; + fontcolor = "black"; + margin = 0.2 + + // Define node colors for different types + "Library" [shape = folder, style = filled, fillcolor = lightblue, label = "Library"]; + + // Package 1 structure + subgraph cluster_package1 { + label = "Package1"; + "Package1" [shape = folder, style = filled, fillcolor = lightgreen, label = "Package1"]; + "Module1_1" [shape = box, style = filled, fillcolor = lightyellow, label = "module1_1.py"]; + "Module1_2" [shape = box, style = filled, fillcolor = lightyellow, label = "module1_2.py"]; + + "ClassA" [shape = ellipse, style = filled, fillcolor = lightcoral, label = "ClassA"]; + "ClassA_method1" [shape = ellipse, style = filled, fillcolor = lightpink, label = "method1()"]; + "ClassA_method2" [shape = ellipse, style = filled, fillcolor = lightpink, label = "method2()"]; + + "ClassC" [shape = ellipse, style = filled, fillcolor = lightcoral, label = "ClassC"]; + "ClassC_method1" [shape = ellipse, style = filled, fillcolor = lightpink, label = "method1()"]; + "ClassC_method2" [shape = ellipse, style = filled, fillcolor = lightpink, label = "method2()"]; + + "function1_1" [shape = ellipse, style = filled, fillcolor = lightgrey, label = "function1_1()"]; + "function1_2" [shape = ellipse, style = filled, fillcolor = lightgrey, label = "function1_2()"]; + + "Package1" -> "Module1_1"; + "Package1" -> "Module1_2"; + + "Module1_1" -> "ClassA"; + "Module1_1" -> "function1_1"; + "Module1_1" -> "function1_2"; + + "ClassA" -> "ClassA_method1"; + "ClassA" -> "ClassA_method2"; + + "Module1_2" -> "ClassC"; + "ClassC" -> "ClassC_method1"; + "ClassC" -> "ClassC_method2"; + } + + // Package 2 structure + subgraph cluster_package2 { + label = "Package2"; + labelloc = "t"; + labeljust = "r"; + + "Package2" [shape = folder, style = filled, fillcolor = lightgreen, label = "Package2"]; + "Module2_1" [shape = box, style = filled, fillcolor = lightyellow, label = "module2_1.py"]; + + "ClassB" [shape = ellipse, style = filled, fillcolor = lightcoral, label = "ClassB"]; + "ClassB_method1" [shape = ellipse, style = filled, fillcolor = lightpink, label = "method1()"]; + + "function2_1" [shape = ellipse, style = filled, fillcolor = lightgrey, label = "function2_1()"]; + "function2_2" [shape = ellipse, style = filled, fillcolor = lightgrey, label = "function2_2()"]; + + "Package2" -> "Module2_1"; + + "Module2_1" -> "ClassB"; + "Module2_1" -> "function2_1"; + "Module2_1" -> "function2_2"; + + "ClassB" -> "ClassB_method1"; + } + + // Relationships from Library to Packages + "Library" -> "Package1"; + "Library" -> "Package2"; +} diff --git a/assets/dummy_figure.svg b/assets/dummy_figure.svg new file mode 100644 index 0000000..9a381a0 --- /dev/null +++ b/assets/dummy_figure.svg @@ -0,0 +1,239 @@ + + + + + + + G + +      Typical + Structure of a Python Library + + + cluster_package1 + + Package1 + + + cluster_package2 + + Package2 + + + + Library + + Library + + + + Package1 + + Package1 + + + + Library->Package1 + + + + + + Package2 + + Package2 + + + + Library->Package2 + + + + + + Module1_1 + + module1_1.py + + + + Package1->Module1_1 + + + + + + Module1_2 + + module1_2.py + + + + Package1->Module1_2 + + + + + + ClassA + + ClassA + + + + Module1_1->ClassA + + + + + + function1_1 + + function1_1() + + + + Module1_1->function1_1 + + + + + + function1_2 + + function1_2() + + + + Module1_1->function1_2 + + + + + + ClassC + + ClassC + + + + Module1_2->ClassC + + + + + + ClassA_method1 + + method1() + + + + ClassA->ClassA_method1 + + + + + + ClassA_method2 + + method2() + + + + ClassA->ClassA_method2 + + + + + + ClassC_method1 + + method1() + + + + ClassC->ClassC_method1 + + + + + + ClassC_method2 + + method2() + + + + ClassC->ClassC_method2 + + + + + + Module2_1 + + module2_1.py + + + + Package2->Module2_1 + + + + + + ClassB + + ClassB + + + + Module2_1->ClassB + + + + + + function2_1 + + function2_1() + + + + Module2_1->function2_1 + + + + + + function2_2 + + function2_2() + + + + Module2_1->function2_2 + + + + + + ClassB_method1 + + method1() + + + + ClassB->ClassB_method1 + + + + + diff --git a/assets/logo.dot b/assets/logo.dot new file mode 100644 index 0000000..37fc81f --- /dev/null +++ b/assets/logo.dot @@ -0,0 +1,10 @@ +digraph G { + // Set the overall style and size of the logo + graph [size = "4,4", ratio = 1, bgcolor = "white"]; + +// Define the node style for the shapes in the logo + node [style = filled, fontcolor = black, shape = box]; + +// Logo element as a square box with a nicer color + logo_title [label = "Logo" shape = box fontcolor = black style = filled color = "#a8e6cf" width = 2.0 height = 2.0 fixedsize = true]; +} diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000..0f8151a --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,19 @@ + + + + + + + G + + + + logo_title + + Logo + + + diff --git a/assets/make_figures.sh b/assets/make_figures.sh new file mode 100644 index 0000000..c69b3ae --- /dev/null +++ b/assets/make_figures.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# You will need to have Graphviz installed to run this script +# You can install it with `sudo apt-get install graphviz` on Debian-based systems + +# Make figures from .dot files +for f in *.dot; do + dot -Tsvg $f -o ${f%.dot}.svg +done diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..f2f3993 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,12 @@ +# Documentation + +Put the documentation for the library in this folder. Typically, you would write the documentation in Markdown or +reStructuredText files. + +## Example + +Example of including an image is shown below: + +

+ +

diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4a5dbb9 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,98 @@ +[tool.poetry] +name = "template-python-library" +version = "0.1.0" +description = "A template repository to help with developing Python libraries." +authors = ["Hassan Abedi "] +maintainers = ["Hassan Abedi "] +license = "MIT" +readme = "README.md" +include = ["README.md", "LICENSE"] +packages = [{ include = "src", from = "." }] +repository = "https://github.com/habedi/template-python-library" +documentation = "https://github.com/habedi/template-python-library/blob/main/docs/index.md" +classifiers = [ + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", +] +keywords = ["template", "python", "library"] + +[tool.poetry.dependencies] +python = "^3.10" +pylint = "^3.0.3" +pytest = "^8.0.1" +pytest-cov = "^5.0.0" +pytest-mock = "^3.14.0" +mypy = "^1.11.1" +poetry-dynamic-versioning = "^1.4.0" +ruff = "^0.6.9" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.pytest.ini_options] +pythonpath = [".", "package"] + +[tool.mypy] +python_version = "3.10" +ignore_missing_imports = true +disallow_untyped_calls = true +strict_optional = true +warn_redundant_casts = true + +[tool.poetry-dynamic-versioning] +enable = true +vcs = "git" +versioning = "semver" # Semantic Versioning + +# Ruff configuration (Edit as needed) +[tool.ruff] +exclude = [ + ".bzr", + ".direnv", + ".eggs", + ".git", + ".git-rewrite", + ".hg", + ".mypy_cache", + ".nox", + ".pants.d", + ".pytype", + ".ruff_cache", + ".svn", + ".tox", + ".venv", + "__pypackages__", + "_build", + "buck-out", + "build", + "dist", + "node_modules", + "venv" +] +line-length = 100 +indent-width = 4 +src = ["src"] +target-version = "py311" + +[tool.ruff.lint] +select = ["ANN", "D", "E", "F", "I"] +ignore = [ + "ANN101", # Don't annotate self + "ANN102" # Don't annotate cls +] +fixable = ["ALL"] +unfixable = [] +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.ruff.pydocstyle] +convention = "google" + +[tool.ruff.per-file-ignores] +"tests/**/*.py" = [] diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/package/__init__.py b/src/package/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/package/module.py b/src/package/module.py new file mode 100644 index 0000000..5b8a352 --- /dev/null +++ b/src/package/module.py @@ -0,0 +1,17 @@ +"""A Python module with a dummy class""" + + +class DummyClass: + def __init__(self): + print('DummyClass.__init__') + + def dummy_method(self): + print('DummyClass.dummy_method') + + @staticmethod + def static_method(): + print('DummyClass.static_method') + + @classmethod + def class_method(cls): + print('DummyClass.class_method') diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_module.py b/tests/test_module.py new file mode 100644 index 0000000..5d0a49f --- /dev/null +++ b/tests/test_module.py @@ -0,0 +1,16 @@ +"""Tests for module.py""" + +from src.package.module import DummyClass + + +# Let's write some unit tests using pytest and arrange-act-assert pattern + +def test_dummy_class(): + # Arrange + dummy = DummyClass() + + # Act + dummy.dummy_method() + + # Assert + assert True