diff --git a/.deepsource.toml b/.deepsource.toml
new file mode 100644
index 0000000..ebe7c8c
--- /dev/null
+++ b/.deepsource.toml
@@ -0,0 +1,41 @@
+version = 1
+exclude_patterns = [
+ "typing-stubs-for-package-name-to-install-with/**",
+]
+test_patterns = [
+ "**/tests/*.py",
+]
+
+[[analyzers]]
+name = "python"
+enabled = true
+dependency_file_paths = [
+ "pyproject.toml",
+ "requirements/requirements.dev.txt",
+ "requirements/requirements.doc.txt",
+ "requirements/requirements.format.txt",
+ "requirements/requirements.lint.txt",
+ "requirements/requirements.release.txt",
+ "requirements/requirements.test.txt",
+ "requirements/requirements.txt",
+]
+
+ [analyzers.meta]
+ runtime_version = "3.x.x"
+ max_line_length = 99
+ skip_doc_coverage = [
+ "init",
+ ]
+ type_checker = "mypy"
+
+[[analyzers]]
+name = "secrets"
+enabled = true
+
+[[transformers]]
+name = "black"
+enabled = true
+
+[[transformers]]
+name = "isort"
+enabled = true
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3ccba44
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,17 @@
+# References
+
+## https://github.com/alexkaratarakis/gitattributes/blob/c9e0391fd0f045478a3d122979eeb23d2311e21a/.gitattributes
+## https://github.com/alexkaratarakis/gitattributes/blob/bf082e21993dc589d27ac403b2c189f24c6e57a1/Common.gitattributes
+## https://github.com/alexkaratarakis/gitattributes/blob/eeb2ca9a67e5985e1c9e00b3d20ae2842eaa8852/Markdown.gitattributes
+## https://github.com/alexkaratarakis/gitattributes/blob/492d0bc3bb09d37cc36e6f1e346b4b61bd620197/Python.gitattributes
+
+* text=auto
+
+*.gitattributes text export-ignore linguist-detectable linguist-language=gitattributes
+*.gitignore text export-ignore linguist-detectable linguist-language=gitignore
+*.gitkeep export-ignore
+
+*.md text diff=markdown linguist-detectable
+*.py text diff=python linguist-detectable
+*.toml text diff=toml linguist-detectable
+*.yaml text diff=yaml linguist-detectable
diff --git a/.github/actions/nox-sessions-action-identifier/action.yml b/.github/actions/nox-sessions-action-identifier/action.yml
new file mode 100644
index 0000000..0c6bd72
--- /dev/null
+++ b/.github/actions/nox-sessions-action-identifier/action.yml
@@ -0,0 +1,33 @@
+name: nox-session-action-name
+description: run nox session
+inputs:
+ session-name-input-identifier:
+ description: name of nox session
+ required: true
+ python-version-input-identifier:
+ description: python version to run session
+ required: false
+ default: none
+runs:
+ using: composite
+ steps:
+ - id: nox-step-identifier
+ name: install nox
+ run: python3 -m pip install nox
+ shell: bash
+ - id: session-all-versions-step-identifier
+ name: run provided nox session for all configured python versions
+ if: env.PYTHON_VERSION == 'none'
+ run: nox --sessions ${{env.SESSION_NAME}} --force-venv-backend venv
+ shell: bash
+ env:
+ SESSION_NAME: ${{ inputs.session-name-input-identifier }}
+ PYTHON_VERSION: ${{ inputs.python-version-input-identifier }}
+ - id: session-single-version-step-identifier
+ name: run provided nox session for provided python version
+ if: env.PYTHON_VERSION != 'none'
+ run: nox --sessions ${{env.SESSION_NAME}} --pythons ${{env.PYTHON_VERSION}} --force-venv-backend venv
+ shell: bash
+ env:
+ SESSION_NAME: ${{ inputs.session-name-input-identifier }}
+ PYTHON_VERSION: ${{ inputs.python-version-input-identifier }}
diff --git a/.github/actions/nox-tags-action-identifier/action.yml b/.github/actions/nox-tags-action-identifier/action.yml
new file mode 100644
index 0000000..5c3b127
--- /dev/null
+++ b/.github/actions/nox-tags-action-identifier/action.yml
@@ -0,0 +1,33 @@
+name: nox-tag-action-name
+description: run nox tag
+inputs:
+ tag-name-input-identifier:
+ description: name of nox tag
+ required: true
+ python-version-input-identifier:
+ description: python version to run tag
+ required: false
+ default: none
+runs:
+ using: composite
+ steps:
+ - id: nox-step-identifier
+ name: install nox
+ run: python3 -m pip install nox
+ shell: bash
+ - id: tag-all-versions-step-identifier
+ name: run provided nox tag for all configured python versions
+ if: env.PYTHON_VERSION == 'none'
+ run: nox --tags ${{env.TAG_NAME}} --force-venv-backend venv
+ shell: bash
+ env:
+ TAG_NAME: ${{ inputs.tag-name-input-identifier }}
+ PYTHON_VERSION: ${{ inputs.python-version-input-identifier }}
+ - id: tag-single-version-step-identifier
+ name: run provided nox tag for provided python version
+ if: env.PYTHON_VERSION != 'none'
+ run: nox --pythons ${PYTHON_VERSION} --tags ${{env.TAG_NAME}} --force-venv-backend venv
+ shell: bash
+ env:
+ TAG_NAME: ${{ inputs.tag-name-input-identifier }}
+ PYTHON_VERSION: ${{ inputs.python-version-input-identifier }}
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..353022c
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,29 @@
+name: docs-workflow-name
+run-name: docs workflow run name
+on: workflow_dispatch
+defaults:
+ run:
+ shell: bash
+jobs:
+ docs-job-identifier:
+ name: docs job name
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout commit
+ uses: actions/checkout@v3
+ - name: set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ check-latest: true
+ architecture: x64
+ cache: pip
+ - name: build documentation
+ uses: ./.github/actions/nox-sessions-action-identifier
+ with:
+ session-name-input-identifier: sphinx
+ - name: upload documentation
+ uses: actions/upload-artifact@v3
+ with:
+ name: docs
+ path: ./docs/build
diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml
new file mode 100644
index 0000000..3623929
--- /dev/null
+++ b/.github/workflows/format.yml
@@ -0,0 +1,37 @@
+name: format-workflow-name
+run-name: format workflow run name
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - opened
+ - synchronize
+defaults:
+ run:
+ shell: bash
+jobs:
+ format-job-identifier:
+ name: format job name
+ strategy:
+ fail-fast: false
+ matrix:
+ runner-platform:
+ - ubuntu-latest
+ python-version:
+ - "3.10"
+ runs-on: ${{ matrix.runner-platform }}
+ steps:
+ - name: checkout commit
+ uses: actions/checkout@v3
+ - name: set up python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ check-latest: true
+ architecture: x64
+ cache: pip
+ - name: run formatters on python ${{ matrix.python-version }}
+ uses: ./.github/actions/nox-tags-action-identifier
+ with:
+ tag-name-input-identifier: format
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..6fcfda6
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,39 @@
+name: lint-workflow-name
+run-name: lint workflow run name
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - opened
+ - synchronize
+defaults:
+ run:
+ shell: bash
+jobs:
+ lint-job-identifier:
+ name: lint job name
+ strategy:
+ fail-fast: false
+ matrix:
+ runner-platform:
+ - ubuntu-latest
+ python-version:
+ - "3.10"
+ - "3.11"
+ runs-on: ${{ matrix.runner-platform }}
+ steps:
+ - name: checkout commit
+ uses: actions/checkout@v3
+ - name: set up python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ check-latest: true
+ architecture: x64
+ cache: pip
+ - name: run linters on python ${{ matrix.python-version }}
+ uses: ./.github/actions/nox-tags-action-identifier
+ with:
+ tag-name-input-identifier: lint
+ python-version-input-identifier: ${{ matrix.python-version }}
diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml
new file mode 100644
index 0000000..708077a
--- /dev/null
+++ b/.github/workflows/pre-commit.yml
@@ -0,0 +1,26 @@
+name: pre-commit-workflow-name
+run-name: pre-commit workflow run name
+on: push
+defaults:
+ run:
+ shell: bash
+jobs:
+ pre-commit-job-identifier:
+ name: pre-commit job name
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout commit
+ uses: actions/checkout@v3
+ - name: set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ check-latest: true
+ architecture: x64
+ cache: pip
+ - name: run all pre-commit hooks
+ env:
+ SKIP: no-commit-to-branch
+ uses: ./.github/actions/nox-sessions-action-identifier
+ with:
+ session-name-input-identifier: pre_commit
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..287d5ec
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,33 @@
+name: release-workflow-name
+run-name: release workflow run name
+on: workflow_dispatch
+defaults:
+ run:
+ shell: bash
+jobs:
+ release-job-identifier:
+ name: release job name
+ runs-on: ubuntu-latest
+ steps:
+ - name: checkout commit
+ uses: actions/checkout@v3
+ - name: set up python
+ uses: actions/setup-python@v4
+ with:
+ python-version: "3.10"
+ check-latest: true
+ architecture: x64
+ cache: pip
+ - name: build and release wheel
+ env:
+ TWINE_REPOSITORY: ${{ secrets.PYPI_REPOSITORY }}
+ TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
+ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
+ uses: ./.github/actions/nox-sessions-action-identifier
+ with:
+ session-name-input-identifier: build
+ - name: upload wheel
+ uses: actions/upload-artifact@v3
+ with:
+ name: release
+ path: ./dist
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..31bef3c
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,50 @@
+name: test-workflow-name
+run-name: test workflow run name
+on:
+ pull_request:
+ branches:
+ - main
+ types:
+ - opened
+ - synchronize
+ push:
+ branches:
+ - main
+defaults:
+ run:
+ shell: bash
+jobs:
+ test-job-identifier:
+ name: test job name
+ strategy:
+ fail-fast: false
+ matrix:
+ runner-platform:
+ - macos-latest
+ - ubuntu-latest
+ - windows-latest
+ python-version:
+ - "3.10"
+ - "3.11"
+ max-parallel: 2
+ runs-on: ${{ matrix.runner-platform }}
+ steps:
+ - name: checkout commit
+ uses: actions/checkout@v3
+ - name: set up python ${{ matrix.python-version }}
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ check-latest: true
+ architecture: x64
+ cache: pip
+ - name: run unit tests on python ${{ matrix.python-version }} in runner ${{ matrix.runner-platform }}
+ uses: ./.github/actions/nox-sessions-action-identifier
+ with:
+ session-name-input-identifier: pytest
+ python-version-input-identifier: ${{ matrix.python-version }}
+ - name: upload coverage reports
+ uses: codecov/codecov-action@v3
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ files: coverage_xml_report.xml
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c793fcb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,272 @@
+# Created by https://www.toptal.com/developers/gitignore/api/linux,macos,windows,visualstudiocode,python
+# Edit at https://www.toptal.com/developers/gitignore?templates=linux,macos,windows,visualstudiocode,python
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### Python ###
+# 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/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+!.vscode/*.code-snippets
+
+# Local History for Visual Studio Code
+.history/
+
+# Built Visual Studio Code Extensions
+*.vsix
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+.ionide
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/linux,macos,windows,visualstudiocode,python
+
+# Custom
+coverage_data
+coverage_html_report/
+coverage_xml_report.xml
+out/
+pytest_junit_report.xml
+typings/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..52fab77
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,152 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.4.0
+ hooks:
+ - id: check-ast
+ - id: check-case-conflict
+ - id: check-json
+ - id: check-merge-conflict
+ - id: check-toml
+ - id: check-yaml
+ - id: end-of-file-fixer
+ - id: mixed-line-ending
+ - id: name-tests-test
+ args:
+ - --pytest-test-first
+ - id: no-commit-to-branch
+ - id: pretty-format-json
+ args:
+ - --autofix
+ - --indent
+ - "4"
+ - id: requirements-txt-fixer
+ - id: trailing-whitespace
+ - repo: https://github.com/asottile/pyupgrade
+ rev: v3.3.1
+ hooks:
+ - id: pyupgrade
+ args:
+ - --py310-plus
+ - repo: https://github.com/pycqa/autoflake
+ rev: v2.0.2
+ hooks:
+ - id: autoflake
+ - repo: https://github.com/pycqa/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+ args:
+ - src
+ pass_filenames: false
+ - repo: https://github.com/psf/black
+ rev: 23.3.0
+ hooks:
+ - id: black
+ args:
+ - src
+ pass_filenames: false
+ - repo: https://github.com/pycqa/bandit
+ rev: 1.7.5
+ hooks:
+ - id: bandit
+ args:
+ - --recursive
+ - --severity-level
+ - high
+ - --confidence-level
+ - high
+ - src
+ pass_filenames: false
+ - repo: https://github.com/pycqa/flake8
+ rev: 6.0.0
+ hooks:
+ - id: flake8
+ args:
+ - --extend-ignore
+ - E203
+ - --per-file-ignores
+ - __init__.py:F401
+ - --max-line-length
+ - "99"
+ - src
+ pass_filenames: false
+ - repo: https://github.com/pre-commit/mirrors-mypy
+ rev: v1.2.0
+ hooks:
+ - id: mypy
+ pass_filenames: false
+ stages:
+ - manual
+ - repo: https://github.com/PyCQA/pylint
+ rev: v3.0.0a6
+ hooks:
+ - id: pylint
+ args:
+ - src
+ pass_filenames: false
+ stages:
+ - manual
+ - repo: https://github.com/RobertCraigie/pyright-python
+ rev: v1.1.302
+ hooks:
+ - id: pyright
+ pass_filenames: false
+ stages:
+ - manual
+ - repo: https://github.com/charliermarsh/ruff-pre-commit
+ rev: v0.0.261
+ hooks:
+ - id: ruff
+ - repo: https://github.com/jendrikseipp/vulture
+ rev: v2.7
+ hooks:
+ - id: vulture
+ pass_filenames: false
+ - repo: https://github.com/PyCQA/docformatter
+ rev: v1.6.0
+ hooks:
+ - id: docformatter
+ additional_dependencies:
+ - tomli
+ args:
+ - --in-place
+ - src
+ pass_filenames: false
+ - repo: https://github.com/adamchainz/blacken-docs
+ rev: "1.13.0"
+ hooks:
+ - id: blacken-docs
+ args:
+ - --line-length
+ - "87"
+ - --target-version
+ - py310
+ - repo: https://github.com/econchick/interrogate
+ rev: 1.5.0
+ hooks:
+ - id: interrogate
+ args:
+ - src
+ pass_filenames: false
+ - repo: https://github.com/pycqa/pydocstyle
+ rev: 6.3.0
+ hooks:
+ - id: pydocstyle
+ additional_dependencies:
+ - tomli
+ args:
+ - src
+ pass_filenames: false
+ - repo: https://github.com/tox-dev/pyproject-fmt
+ rev: 0.9.2
+ hooks:
+ - id: pyproject-fmt
+ - repo: https://github.com/abravalheri/validate-pyproject
+ rev: v0.12.2
+ hooks:
+ - id: validate-pyproject
+ - repo: https://github.com/codespell-project/codespell
+ rev: v2.2.4
+ hooks:
+ - id: codespell
+fail_fast: false
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..f2c365d
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,14 @@
+version: 2
+python:
+ install:
+ - path: .
+ method: pip
+ extra_requirements:
+ - doc
+build:
+ os: ubuntu-22.04
+ tools:
+ python: "3.10"
+sphinx:
+ builder: html
+ configuration: docs/source/conf.py
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..7c12348
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022-2023 Anirban Ray, First Maintainer, Second Maintainer
+
+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/Makefile b/Makefile
new file mode 100644
index 0000000..9de22b7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,418 @@
+MAKEFLAGS += --silent
+
+SHELL := /usr/bin/env
+.SHELLFLAGS := bash -c -e +x
+
+.DEFAULT_GOAL := help
+
+PYTHON_DEPENDENCIES_DIRECTORY := ./requirements
+
+PYTHON_SOURCE_DIRECTORY := ./src
+PYTHON_DOCS_DIRECTORY := ./docs
+PYTHON_DIST_DIRECTORY := ./dist
+
+PYTHON_SOURCE_SCRIPTS_PATTERN := "${PYTHON_SOURCE_DIRECTORY}/**/*.py"
+PYTHON_TEST_SCRIPTS_PATTERN := "**/tests/*.py"
+PYTHON_SKIP_TEST_SCRIPTS_PATTERN := "./src/*tests*"
+
+PYTHON_SOURCE_SCRIPTS := $(shell find ${PYTHON_SOURCE_DIRECTORY} -type f -name "*.py")
+
+define check_install_status =
+ $(shell python3 -m pip show ${1} > /dev/null 2>&1)
+ if [ ${.SHELLSTATUS} -eq 1 ]; then
+ python3 -m pip install --upgrade ${1}
+ fi
+endef
+
+## help
+## list all wrapper targets
+## show documentaions of wrapper targets
+.PHONY: help
+help: Makefile
+ @sed -n 's/^## //p' $<
+
+.ONESHELL:
+venv:
+ python3 -m venv venv
+ echo "*" > venv/.gitignore
+ source venv/bin/activate
+ python3 -m pip install --upgrade pip setuptools wheel
+ python3 -m pip install --editable ".[all]"
+
+.ONESHELL:
+.PHONY: pre-commit-install
+pre-commit-install: venv
+ source venv/bin/activate
+ $(call check_install_status,pre-commit)
+ pre-commit install
+
+## setup
+## create isolated environment with editable package and dependencies (venv)
+## add pre-commit to git hooks (pre-commit-install)
+.PHONY: setup
+setup: venv pre-commit-install
+
+.ONESHELL:
+venv-upgrade: venv
+ source venv/bin/activate
+ python3 -m pip install --upgrade pip setuptools wheel
+ python3 -m pip install --upgrade \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.txt \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.dev.txt \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.doc.txt \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.format.txt \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.lint.txt \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.release.txt \
+ --requirement ${PYTHON_DEPENDENCIES_DIRECTORY}/requirements.test.txt
+
+.ONESHELL:
+.PHONY: pre-commit-autoupdate
+pre-commit-autoupdate: venv
+ source venv/bin/activate
+ pre-commit autoupdate
+
+## update
+## upgrade isolated environment to latest package and dependencies (venv-upgrade)
+## update versions of pre-commit hooks (pre-commit-autoupdate)
+.PHONY: update
+update: venv-upgrade pre-commit-autoupdate
+
+.ONESHELL:
+.PHONY: autoflake
+autoflake: venv
+ source venv/bin/activate
+ $(call check_install_status,autoflake)
+ autoflake $(PYTHON_SOURCE_SCRIPTS)
+
+.ONESHELL:
+.PHONY: black
+black: venv
+ source venv/bin/activate
+ $(call check_install_status,black)
+ black ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: blacken-docs
+blacken-docs: venv
+ source venv/bin/activate
+ $(call check_install_status,blacken-docs)
+ blacken-docs \
+ --line-length 87 \
+ --target-version py310 \
+ $(PYTHON_SOURCE_SCRIPTS)
+
+.ONESHELL:
+.PHONY: docformatter
+docformatter: venv
+ source venv/bin/activate
+ $(call check_install_status,docformatter)
+ $(call check_install_status,tomli)
+ docformatter ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: isort
+isort: venv
+ source venv/bin/activate
+ $(call check_install_status,isort)
+ isort ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: pyupgrade
+pyupgrade: venv
+ source venv/bin/activate
+ $(call check_install_status,pyupgrade)
+ pyupgrade --py310-plus $(PYTHON_SOURCE_SCRIPTS)
+
+## format
+## change codes for older versions (pyupgrade)
+## remove pyflake detected issues (autoflake)
+## sort imports (isort)
+## format docstrings (docformatter)
+## format code style (black)
+.PHONY: format
+format: pyupgrade autoflake isort docformatter blacken-docs black
+
+.ONESHELL:
+.PHONY: bandit
+bandit: venv
+ source venv/bin/activate
+ $(call check_install_status,bandit)
+ bandit \
+ --recursive \
+ --severity-level high \
+ --confidence-level high \
+ ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: flake8
+flake8: venv
+ source venv/bin/activate
+ $(call check_install_status,flake8)
+ flake8 \
+ --extend-ignore E203 \
+ --per-file-ignores __init__.py:F401 \
+ --max-line-length 99 \
+ ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: interrogate
+interrogate: venv
+ source venv/bin/activate
+ $(call check_install_status,interrogate)
+ interrogate ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: pydocstyle
+pydocstyle: venv
+ source venv/bin/activate
+ $(call check_install_status,pydocstyle)
+ $(call check_install_status,tomli)
+ pydocstyle ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: pylint
+pylint: venv
+ source venv/bin/activate
+ $(call check_install_status,pylint)
+ pylint ${PYTHON_SOURCE_DIRECTORY}
+
+.ONESHELL:
+.PHONY: vulture
+vulture: venv
+ source venv/bin/activate
+ $(call check_install_status,vulture)
+ vulture
+
+## lint
+## find security issues (bandit)
+## lint all python scripts (flake8)
+## check docstring coverage (interrogate)
+## check docstring presence and formats (pydocstyle)
+## lint all python scripts (pylint)
+## find dead code (vulture)
+.PHONY: lint
+lint: bandit flake8 interrogate pydocstyle pylint vulture
+
+.ONESHELL:
+.PHONY: pytest-doctest
+pytest-doctest: venv
+ source venv/bin/activate
+ $(call check_install_status,pytest)
+ pytest -k "not test_"
+
+.ONESHELL:
+.PHONY: pytest-failure
+pytest-failure: venv
+ source venv/bin/activate
+ $(call check_install_status,pytest)
+ pytest -k "failure"
+
+.ONESHELL:
+.PHONY: pytest-hypotheses
+pytest-hypotheses: venv
+ source venv/bin/activate
+ $(call check_install_status,hypothesis)
+ $(call check_install_status,pytest)
+ pytest -k "hypothesis"
+
+.ONESHELL:
+.PHONY: pytest-others
+pytest-others: venv
+ source venv/bin/activate
+ $(call check_install_status,pytest)
+ pytest -k "test and not failure and not hypothesis and not successful"
+
+.ONESHELL:
+.PHONY: pytest-successful
+pytest-successful: venv
+ source venv/bin/activate
+ $(call check_install_status,pytest)
+ pytest -k "successful"
+
+## test
+## test doctests (pytest-doctest)
+## test successful operations (pytest-successful)
+## test failed operations (pytest-failure)
+## test hypotheses (pytest-others)
+## test other unit tests (pytest-others)
+.PHONY: test
+test: pytest-doctest pytest-successful pytest-failure pytest-hypotheses pytest-others
+
+.ONESHELL:
+.PHONY: coverage-erase
+coverage-erase: venv
+ source venv/bin/activate
+ $(call check_install_status,coverage)
+ $(call check_install_status,tomli)
+ coverage erase
+
+.ONESHELL:
+.PHONY: coverage-html
+coverage-html: venv
+ source venv/bin/activate
+ $(call check_install_status,coverage)
+ $(call check_install_status,tomli)
+ coverage html
+
+.ONESHELL:
+.PHONY: coverage-report
+coverage-report: venv
+ source venv/bin/activate
+ $(call check_install_status,coverage)
+ $(call check_install_status,tomli)
+ coverage report
+
+.ONESHELL:
+.PHONY: coverage-run
+coverage-run: venv
+ source venv/bin/activate
+ $(call check_install_status,coverage)
+ $(call check_install_status,hypothesis)
+ $(call check_install_status,pytest)
+ $(call check_install_status,tomli)
+ coverage run
+
+.ONESHELL:
+.PHONY: coverage-xml
+coverage-xml: venv
+ source venv/bin/activate
+ $(call check_install_status,coverage)
+ $(call check_install_status,tomli)
+ coverage xml
+
+## coverage
+## test all doctests and unit tests (coverage-run)
+## create test coverage report (coverage-html)
+## delete collected coverage data (coverage-erase)
+.PHONY: coverage
+coverage: coverage-run coverage-report coverage-html coverage-xml coverage-erase
+
+.ONESHELL:
+.PHONY: sphinx-source
+sphinx-source: venv
+ source venv/bin/activate
+ $(call check_install_status,Sphinx)
+ sphinx-apidoc \
+ --output-dir ${PYTHON_DOCS_DIRECTORY}/source \
+ --maxdepth 3 \
+ --force \
+ --follow-links \
+ --separate \
+ ${PYTHON_SOURCE_DIRECTORY} \
+ ${PYTHON_SKIP_TEST_SCRIPTS_PATTERN}
+
+.ONESHELL:
+.PHONY: sphinx-build
+sphinx-build: venv
+ source venv/bin/activate
+ $(call check_install_status,furo)
+ $(call check_install_status,Sphinx)
+ $(call check_install_status,sphinx-copybutton)
+ sphinx-build \
+ -b html \
+ ${PYTHON_DOCS_DIRECTORY}/source \
+ ${PYTHON_DOCS_DIRECTORY}/build
+
+## docs
+## prepare documentation sources with directives (sphinx-source)
+## create HTML documentation from source files (sphinx-build)
+.PHONY: docs
+docs: sphinx-source sphinx-build
+
+.ONESHELL:
+.PHONY: build
+build: venv
+ source venv/bin/activate
+ $(call check_install_status,build)
+ python3 -m build --outdir ${PYTHON_DIST_DIRECTORY}
+
+.ONESHELL:
+.PHONY: twine-check
+twine-check: venv
+ source venv/bin/activate
+ $(call check_install_status,twine)
+ twine check --strict ${PYTHON_DIST_DIRECTORY}/*
+
+.ONESHELL:
+.PHONY: twine-upload
+twine-upload: venv
+ source venv/bin/activate
+ $(call check_install_status,twine)
+ twine upload ${PYTHON_DIST_DIRECTORY}/*
+
+## release
+## create distribution files (build)
+## check package description (twine-check)
+## upload distribution files (twine-upload)
+.PHONY: release
+release: build twine-check twine-upload
+
+.PHONY: clean-coverage
+clean-coverage:
+ find . \
+ -type f -name .coverage -delete \
+ -o \
+ -type d -name htmlcov \
+ -exec rm -r "{}" +
+
+.PHONY: clean-mypy_cache
+clean-mypy_cache:
+ find . \
+ -type d -name .mypy_cache \
+ -exec rm -r "{}" +
+
+.PHONY: clean-pycache
+clean-pycache:
+ find . \
+ -type f -name '*.py[co]' -delete \
+ -o \
+ -type d -name __pycache__ -delete
+
+.PHONY: clean-pytest_cache
+clean-pytest_cache:
+ find . \
+ -type d -name .pytest_cache \
+ -exec rm -r "{}" +
+
+## cleanup
+## delete all pycache directories and other cache files (clean-pycache)
+## delete all mypy cache (clean-mypy_cache)
+## delete all unit test cache (clean-pytest_cache)
+## delete all coverage results (clean-coverage)
+.PHONY: cleanup
+cleanup: clean-pycache clean-mypy_cache clean-pytest_cache clean-coverage
+
+.ONESHELL:
+.PHONY: mypy
+mypy: venv
+ source venv/bin/activate
+ $(call check_install_status,mypy)
+ mypy
+
+.ONESHELL:
+.PHONY: mypy-stubgen
+mypy-stubgen: venv
+ source venv/bin/activate
+ $(call check_install_status,mypy)
+ stubgen \
+ --package package_name_to_import_with \
+ --module module_that_can_be_imported_directly \
+ --module module_that_can_be_invoked_from_cli \
+ --module module_that_can_invoke_gui_from_cli
+
+.ONESHELL:
+.PHONY: pyright
+pyright: venv
+ source venv/bin/activate
+ $(call check_install_status,pyright)
+ pyright
+
+.ONESHELL:
+.PHONY: pyright-stubs
+pyright-stubs: venv
+ source venv/bin/activate
+ $(call check_install_status,pyright)
+ pyright --createstub package_name_to_import_with
+ pyright --createstub module_that_can_be_imported_directly
+ pyright --createstub module_that_can_be_invoked_from_cli
+ pyright --createstub module_that_can_invoke_gui_from_cli
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d761bb0
--- /dev/null
+++ b/README.md
@@ -0,0 +1,117 @@
+# Learn Python Packaging
+
+[![codecov][codecov-badge-image]][codecov-badge-url]
+[![DeepSource][deepsource-badge-image]][deepsource-badge-url]
+[![Documentation Status][read-the-docs-badge-image]][read-the-docs-badge-url]
+
+[![docs workflow][docs-workflow-badge-image]][docs-workflow-badge-url]
+[![format workflow][format-workflow-badge-image]][format-workflow-badge-url]
+[![lint workflow][lint-workflow-badge-image]][lint-workflow-badge-url]
+[![pre-commit workflow][pre-commit-workflow-badge-image]][pre-commit-workflow-badge-url]
+[![release workflow][release-workflow-badge-image]][release-workflow-badge-url]
+[![test workflow][test-workflow-badge-image]][test-workflow-badge-url]
+
+[![pre-commit][pre-commit-badge-image]][pre-commit-badge-url]
+[![Ruff][ruff-badge-image]][ruff-badge-url]
+
+[![Code style: black][black-badge-image]][black-badge-url]
+[![Docstring formatter: docformatter][docformatter-badge-image]][docformatter-badge-url]
+[![Imports: isort][isort-badge-image]][isort-badge-url]
+
+[![security: bandit][bandit-badge-image]][bandit-badge-url]
+[![linting: pylint][pylint-badge-image]][pylint-badge-url]
+
+[![Docstring style: numpy][numpydoc-badge-image]][numpydoc-badge-url]
+
+- [x] ~~Create python package~~
+- [x] ~~Build wheel~~
+- [x] ~~Release wheel~~
+- [x] ~~Setup Github Actions~~
+- [x] ~~Host documentation~~
+- [x] ~~Hypotheses testing~~
+
+## Tools Used
+
+- Development
+ - [codespell](https://github.com/codespell-project/codespell)
+ - [Nox](https://github.com/wntrblm/nox)
+ - [pre-commit](https://github.com/pre-commit/pre-commit)
+ - [Ruff](https://github.com/charliermarsh/ruff)
+- Formatting
+ - [autoflake](https://www.github.com/PyCQA/autoflake)
+ - [Black](https://github.com/psf/black)
+ - [blacken-docs](https://github.com/adamchainz/blacken-docs)
+ - [docformatter](https://github.com/PyCQA/docformatter)
+ - [isort](https://pycqa.github.io/isort/)
+ - [pyproject-fmt](https://github.com/tox-dev/pyproject-fmt)
+ - [pyupgrade](https://github.com/asottile/pyupgrade)
+- Linting
+ - [Bandit](https://bandit.readthedocs.io/)
+ - [Flake8](https://github.com/pycqa/flake8)
+ - [interrogate](https://interrogate.readthedocs.io/)
+ - [Mypy](http://www.mypy-lang.org/)
+ - [pydocstyle](https://www.pydocstyle.org/en/stable/)
+ - [Pylint](https://github.com/PyCQA/pylint)
+ - [Pyright for Python](https://github.com/RobertCraigie/pyright-python)
+ - [validate-pyproject](https://github.com/abravalheri/validate-pyproject/)
+ - [Vulture](https://github.com/jendrikseipp/vulture)
+- Testing
+ - [Coverage.py](https://github.com/nedbat/coveragepy)
+ - [Hypothesis](https://hypothesis.works/)
+ - [pytest](https://docs.pytest.org/en/latest/)
+- Documentation
+ - [Furo](https://github.com/pradyunsg/furo)
+ - [Read the Docs](https://readthedocs.org/)
+ - [Sphinx](https://www.sphinx-doc.org/)
+ - [sphinx-copybutton](https://github.com/executablebooks/sphinx-copybutton)
+
+[bandit-badge-image]: https://img.shields.io/badge/security-bandit-yellow.svg
+[bandit-badge-url]: https://github.com/PyCQA/bandit
+
+[black-badge-image]: https://img.shields.io/badge/code%20style-black-000000.svg
+[black-badge-url]: https://github.com/psf/black
+
+[docformatter-badge-image]: https://img.shields.io/badge/%20formatter-docformatter-fedcba.svg
+[docformatter-badge-url]: https://github.com/PyCQA/docformatter
+
+[codecov-badge-image]: https://codecov.io/gh/yarnabrina/learn-python-packaging/branch/main/graph/badge.svg?token=BG1ECA7E14
+[codecov-badge-url]: https://codecov.io/gh/yarnabrina/learn-python-packaging
+
+[deepsource-badge-image]: https://deepsource.io/gh/yarnabrina/learn-python-packaging.svg/?label=active+issues&token=tfsfTm2RCqlPTgF3dN31q-0e
+[deepsource-badge-url]: https://deepsource.io/gh/yarnabrina/learn-python-packaging/?ref=repository-badge
+
+[docs-workflow-badge-image]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/docs.yml/badge.svg
+[docs-workflow-badge-url]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/docs.yml/
+
+[format-workflow-badge-image]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/format.yml/badge.svg
+[format-workflow-badge-url]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/format.yml/
+
+[isort-badge-image]: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336
+[isort-badge-url]: https://pycqa.github.io/isort/
+
+[lint-workflow-badge-image]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/lint.yml/badge.svg
+[lint-workflow-badge-url]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/lint.yml/
+
+[numpydoc-badge-image]: https://img.shields.io/badge/%20style-numpy-459db9.svg
+[numpydoc-badge-url]: https://numpydoc.readthedocs.io/en/latest/format.html
+
+[pre-commit-badge-image]: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit
+[pre-commit-badge-url]: https://github.com/pre-commit/pre-commit
+
+[pre-commit-workflow-badge-image]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/pre-commit.yml/badge.svg
+[pre-commit-workflow-badge-url]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/pre-commit.yml/
+
+[pylint-badge-image]: https://img.shields.io/badge/linting-pylint-yellowgreen
+[pylint-badge-url]: https://github.com/PyCQA/pylint
+
+[read-the-docs-badge-image]: https://readthedocs.org/projects/learn-python-packaging/badge/?version=latest
+[read-the-docs-badge-url]: https://learn-python-packaging.readthedocs.io/en/latest/?badge=latest
+
+[release-workflow-badge-image]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/release.yml/badge.svg
+[release-workflow-badge-url]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/release.yml/
+
+[ruff-badge-image]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/charliermarsh/ruff/main/assets/badge/v0.json
+[ruff-badge-url]: https://github.com/charliermarsh/ruff
+
+[test-workflow-badge-image]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/test.yml/badge.svg
+[test-workflow-badge-url]: https://github.com/yarnabrina/learn-python-packaging/actions/workflows/test.yml/
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..dc1312a
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/source/conf.py b/docs/source/conf.py
new file mode 100644
index 0000000..e8c4bc3
--- /dev/null
+++ b/docs/source/conf.py
@@ -0,0 +1,69 @@
+"""Configure Sphinx documentation."""
+# pylint: disable=invalid-name
+import sys
+
+import package_name_to_import_with
+
+sys.path.insert(0, "../src")
+
+project = "package-name-to-install-with"
+version = str(package_name_to_import_with.__version__)
+project_copyright = "2022-2023, Anirban Ray, First Maintainer, Second Maintainer"
+author = "Anirban Ray, First Author, Second Author"
+release = f"v{version}"
+
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.coverage",
+ "sphinx.ext.doctest",
+ "sphinx.ext.napoleon",
+ "sphinx.ext.todo",
+ "sphinx.ext.viewcode",
+ "sphinx_copybutton",
+]
+
+smartquotes = False
+today_fmt = "%Y-%m-%d"
+highlight_language = "python3"
+pygments_style = "friendly"
+add_function_parentheses = False
+add_module_names = False
+trim_doctest_flags = True
+
+html_theme = "furo"
+html_theme_options = {
+ "top_of_page_button": None,
+ "announcement": "all page banner",
+}
+html_title = f"Title of {project}'s Documentation at Release {release}"
+
+html_last_updated_fmt = "%B %d, %Y"
+html_use_index = True
+html_split_index = False
+html_copy_source = False
+html_show_sourcelink = False
+html_show_sphinx = False
+html_output_encoding = "utf-8"
+
+autoclass_content = "class"
+autodoc_inherit_docstrings = True
+autodoc_member_order = "bysource"
+autodoc_typehints = "description"
+autodoc_typehints_description_target = "documented"
+autodoc_typehints_format = "fully-qualified"
+
+napoleon_google_docstring = False
+napoleon_numpy_docstring = True
+napoleon_include_init_with_doc = False
+napoleon_use_param = True
+napoleon_use_keyword = True
+napoleon_use_rtype = True
+napoleon_preprocess_types = True
+
+todo_include_todos = True
+
+viewcode_follow_imported_members = True
+
+copybutton_prompt_text = r">>> |\.\.\. |\$ "
+copybutton_prompt_is_regexp = True
+copybutton_line_continuation_character = "\\"
diff --git a/docs/source/index.rst b/docs/source/index.rst
new file mode 100644
index 0000000..ab3d669
--- /dev/null
+++ b/docs/source/index.rst
@@ -0,0 +1,19 @@
+Welcome to the documentation of package-name-to-install-with!
+=============================================================
+
+**Date**: |today|
+
+**Version**: |version|
+
+.. toctree::
+ :maxdepth: 1
+ :caption: Contents:
+
+ modules
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/source/module_that_can_be_imported_directly.rst b/docs/source/module_that_can_be_imported_directly.rst
new file mode 100644
index 0000000..646b584
--- /dev/null
+++ b/docs/source/module_that_can_be_imported_directly.rst
@@ -0,0 +1,7 @@
+module\_that\_can\_be\_imported\_directly module
+================================================
+
+.. automodule:: module_that_can_be_imported_directly
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/module_that_can_be_invoked_from_cli.rst b/docs/source/module_that_can_be_invoked_from_cli.rst
new file mode 100644
index 0000000..490f2ab
--- /dev/null
+++ b/docs/source/module_that_can_be_invoked_from_cli.rst
@@ -0,0 +1,7 @@
+module\_that\_can\_be\_invoked\_from\_cli module
+================================================
+
+.. automodule:: module_that_can_be_invoked_from_cli
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/module_that_can_invoke_gui_from_cli.rst b/docs/source/module_that_can_invoke_gui_from_cli.rst
new file mode 100644
index 0000000..e7a8206
--- /dev/null
+++ b/docs/source/module_that_can_invoke_gui_from_cli.rst
@@ -0,0 +1,7 @@
+module\_that\_can\_invoke\_gui\_from\_cli module
+================================================
+
+.. automodule:: module_that_can_invoke_gui_from_cli
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/modules.rst b/docs/source/modules.rst
new file mode 100644
index 0000000..a27fc6f
--- /dev/null
+++ b/docs/source/modules.rst
@@ -0,0 +1,10 @@
+src
+===
+
+.. toctree::
+ :maxdepth: 3
+
+ module_that_can_be_imported_directly
+ module_that_can_be_invoked_from_cli
+ module_that_can_invoke_gui_from_cli
+ package_name_to_import_with
diff --git a/docs/source/package_name_to_import_with.calculator_sub_package.inverses_module.rst b/docs/source/package_name_to_import_with.calculator_sub_package.inverses_module.rst
new file mode 100644
index 0000000..d2551de
--- /dev/null
+++ b/docs/source/package_name_to_import_with.calculator_sub_package.inverses_module.rst
@@ -0,0 +1,7 @@
+package\_name\_to\_import\_with.calculator\_sub\_package.inverses\_module module
+================================================================================
+
+.. automodule:: package_name_to_import_with.calculator_sub_package.inverses_module
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.calculator_sub_package.operations_module.rst b/docs/source/package_name_to_import_with.calculator_sub_package.operations_module.rst
new file mode 100644
index 0000000..7542867
--- /dev/null
+++ b/docs/source/package_name_to_import_with.calculator_sub_package.operations_module.rst
@@ -0,0 +1,7 @@
+package\_name\_to\_import\_with.calculator\_sub\_package.operations\_module module
+==================================================================================
+
+.. automodule:: package_name_to_import_with.calculator_sub_package.operations_module
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.calculator_sub_package.rst b/docs/source/package_name_to_import_with.calculator_sub_package.rst
new file mode 100644
index 0000000..4cdad56
--- /dev/null
+++ b/docs/source/package_name_to_import_with.calculator_sub_package.rst
@@ -0,0 +1,21 @@
+package\_name\_to\_import\_with.calculator\_sub\_package package
+================================================================
+
+Submodules
+----------
+
+.. toctree::
+ :maxdepth: 3
+
+ package_name_to_import_with.calculator_sub_package.inverses_module
+ package_name_to_import_with.calculator_sub_package.operations_module
+ package_name_to_import_with.calculator_sub_package.utility_module
+ package_name_to_import_with.calculator_sub_package.wrapper_module
+
+Module contents
+---------------
+
+.. automodule:: package_name_to_import_with.calculator_sub_package
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.calculator_sub_package.utility_module.rst b/docs/source/package_name_to_import_with.calculator_sub_package.utility_module.rst
new file mode 100644
index 0000000..25aaa9c
--- /dev/null
+++ b/docs/source/package_name_to_import_with.calculator_sub_package.utility_module.rst
@@ -0,0 +1,7 @@
+package\_name\_to\_import\_with.calculator\_sub\_package.utility\_module module
+===============================================================================
+
+.. automodule:: package_name_to_import_with.calculator_sub_package.utility_module
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.calculator_sub_package.wrapper_module.rst b/docs/source/package_name_to_import_with.calculator_sub_package.wrapper_module.rst
new file mode 100644
index 0000000..0cc2b6d
--- /dev/null
+++ b/docs/source/package_name_to_import_with.calculator_sub_package.wrapper_module.rst
@@ -0,0 +1,7 @@
+package\_name\_to\_import\_with.calculator\_sub\_package.wrapper\_module module
+===============================================================================
+
+.. automodule:: package_name_to_import_with.calculator_sub_package.wrapper_module
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.data_using_module.rst b/docs/source/package_name_to_import_with.data_using_module.rst
new file mode 100644
index 0000000..145d9cc
--- /dev/null
+++ b/docs/source/package_name_to_import_with.data_using_module.rst
@@ -0,0 +1,7 @@
+package\_name\_to\_import\_with.data\_using\_module module
+==========================================================
+
+.. automodule:: package_name_to_import_with.data_using_module
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.garbage_collection_module.rst b/docs/source/package_name_to_import_with.garbage_collection_module.rst
new file mode 100644
index 0000000..df38b23
--- /dev/null
+++ b/docs/source/package_name_to_import_with.garbage_collection_module.rst
@@ -0,0 +1,7 @@
+package\_name\_to\_import\_with.garbage\_collection\_module module
+==================================================================
+
+.. automodule:: package_name_to_import_with.garbage_collection_module
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/docs/source/package_name_to_import_with.rst b/docs/source/package_name_to_import_with.rst
new file mode 100644
index 0000000..63e2d2d
--- /dev/null
+++ b/docs/source/package_name_to_import_with.rst
@@ -0,0 +1,27 @@
+package\_name\_to\_import\_with package
+=======================================
+
+Subpackages
+-----------
+
+.. toctree::
+ :maxdepth: 3
+
+ package_name_to_import_with.calculator_sub_package
+
+Submodules
+----------
+
+.. toctree::
+ :maxdepth: 3
+
+ package_name_to_import_with.data_using_module
+ package_name_to_import_with.garbage_collection_module
+
+Module contents
+---------------
+
+.. automodule:: package_name_to_import_with
+ :members:
+ :undoc-members:
+ :show-inheritance:
diff --git a/noxfile.py b/noxfile.py
new file mode 100644
index 0000000..b6c2c56
--- /dev/null
+++ b/noxfile.py
@@ -0,0 +1,332 @@
+"""Configure nox."""
+import functools
+import pathlib
+
+import nox
+
+PYTHON_DEFAULT_VERSION = "3.10"
+PYTHON_VERSIONS = ["3.10", "3.11"]
+
+SOURCE_DIRECTORY = pathlib.Path("src")
+DIST_DIRECTORY = pathlib.Path("dist")
+DOCS_DIRECTORY = pathlib.Path("docs")
+
+PYTHON_SCRIPT_PATHS = SOURCE_DIRECTORY.glob("**/*.py")
+PYTHON_SCRIPTS = [str(SCRIPT_PATH) for SCRIPT_PATH in PYTHON_SCRIPT_PATHS]
+
+GENERAL_SESSION_DECORATOR = functools.partial(nox.session, venv_backend="conda", reuse_venv=True)
+
+FORMAT_SESSION_DECORATOR = functools.partial(
+ GENERAL_SESSION_DECORATOR, python=PYTHON_DEFAULT_VERSION, tags=["format"]
+)
+LINT_SESSION_DECORATOR = functools.partial(
+ GENERAL_SESSION_DECORATOR, python=PYTHON_VERSIONS, tags=["lint"]
+)
+RELEASE_SESSION_DECORATOR = functools.partial(
+ GENERAL_SESSION_DECORATOR, python=PYTHON_DEFAULT_VERSION, tags=["release"]
+)
+TEST_SESSION_DECORATOR = functools.partial(
+ GENERAL_SESSION_DECORATOR, python=PYTHON_VERSIONS, tags=["test"]
+)
+
+
+@FORMAT_SESSION_DECORATOR
+def autoflake(session: nox.Session) -> None:
+ """Run autoflake.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("autoflake")
+
+ session.run("autoflake", *PYTHON_SCRIPTS)
+
+
+@LINT_SESSION_DECORATOR
+def bandit(session: nox.Session) -> None:
+ """Run bandit.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("bandit")
+
+ session.run(
+ "bandit",
+ "--recursive",
+ "--severity-level",
+ "high",
+ "--confidence-level",
+ "high",
+ str(SOURCE_DIRECTORY),
+ )
+
+
+@FORMAT_SESSION_DECORATOR
+def black(session: nox.Session) -> None:
+ """Run black.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("black")
+
+ session.run("black", *PYTHON_SCRIPTS)
+
+
+@FORMAT_SESSION_DECORATOR
+def blacken_docs(session: nox.Session) -> None:
+ """Run blacken-docs.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("blacken-docs")
+
+ session.run("blacken-docs", *PYTHON_SCRIPTS)
+
+
+@RELEASE_SESSION_DECORATOR
+def build(session: nox.Session) -> None:
+ """Run build."""
+ session.install("build", "wheel")
+
+ session.run("python3", "-m", "build", "--outdir", f"{DIST_DIRECTORY.name}", "--no-isolation")
+
+ session.notify("twine")
+
+
+@TEST_SESSION_DECORATOR
+def coverage(session: nox.Session) -> None:
+ """Run coverage.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("coverage[toml]")
+
+ session.run("coverage", "report")
+ session.run("coverage", "html")
+ session.run("coverage", "xml")
+
+
+@FORMAT_SESSION_DECORATOR
+def docformatter(session: nox.Session) -> None:
+ """Run docformatter.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("docformatter[tomli]")
+
+ session.run("docformatter", *PYTHON_SCRIPTS)
+
+
+@LINT_SESSION_DECORATOR
+def flake8(session: nox.Session) -> None:
+ """Run flake8.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("flake8")
+
+ session.run(
+ "flake8",
+ "--extend-ignore",
+ "E203",
+ "--per-file-ignores",
+ "__init__.py:F401",
+ "--max-line-length",
+ "99",
+ *PYTHON_SCRIPTS,
+ )
+
+
+@LINT_SESSION_DECORATOR
+def interrogate(session: nox.Session) -> None:
+ """Run interrogate.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("interrogate")
+
+ session.run("interrogate", str(SOURCE_DIRECTORY))
+
+
+@FORMAT_SESSION_DECORATOR
+def isort(session: nox.Session) -> None:
+ """Run isort.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("isort")
+
+ session.run("isort", *PYTHON_SCRIPTS)
+
+
+@LINT_SESSION_DECORATOR
+def mypy(session: nox.Session) -> None:
+ """Run mypy.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("mypy")
+
+ session.run("mypy")
+
+
+@GENERAL_SESSION_DECORATOR(python=PYTHON_DEFAULT_VERSION)
+def pre_commit(session: nox.Session) -> None:
+ """Run pre-commit.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("pre-commit")
+
+ session.run(
+ "pre-commit",
+ "run",
+ "--color",
+ "always",
+ "--verbose",
+ "--all-files",
+ "--hook-stage",
+ "manual",
+ )
+
+
+@LINT_SESSION_DECORATOR
+def pydocstyle(session: nox.Session) -> None:
+ """Run pydocstyle.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("pydocstyle")
+
+ session.run("pydocstyle", str(SOURCE_DIRECTORY))
+
+
+@LINT_SESSION_DECORATOR
+def pylint(session: nox.Session) -> None:
+ """Run pylint.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("pylint")
+
+ session.run("pylint", "--disable", "import-error", str(SOURCE_DIRECTORY))
+
+
+@LINT_SESSION_DECORATOR
+def pyright(session: nox.Session) -> None:
+ """Run pyright.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("pyright")
+
+ session.run("pyright")
+
+
+@TEST_SESSION_DECORATOR
+def pytest(session: nox.Session) -> None:
+ """Run pytest.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("-e", ".[test]")
+
+ session.run("coverage", "run")
+
+ session.notify("coverage")
+
+
+@FORMAT_SESSION_DECORATOR
+def pyupgrade(session: nox.Session) -> None:
+ """Run pyupgrade.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("pyupgrade")
+
+ session.run("pyupgrade", "--py310-plus", *PYTHON_SCRIPTS)
+
+
+@GENERAL_SESSION_DECORATOR(python=PYTHON_DEFAULT_VERSION)
+def sphinx(session: nox.Session) -> None:
+ """Run sphinx.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("-e", ".[doc]")
+
+ with session.chdir("docs"):
+ session.run("sphinx-build", "-b", "html", "source", "build")
+
+
+@RELEASE_SESSION_DECORATOR
+def twine(session: nox.Session) -> None:
+ """Run twine."""
+ session.install("twine")
+
+ session.run("twine", "check", f"{DIST_DIRECTORY.name}/*")
+ session.run("twine", "upload", f"{DIST_DIRECTORY.name}/*")
+
+
+@LINT_SESSION_DECORATOR
+def vulture(session: nox.Session) -> None:
+ """Run vulture.
+
+ Parameters
+ ----------
+ session : nox.Session
+ nox Session object
+ """
+ session.install("vulture")
+
+ session.run("vulture")
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..0539f5a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,345 @@
+[build-system]
+build-backend = "setuptools.build_meta"
+requires = [
+ "setuptools>=61",
+]
+
+[project]
+name = "package-name-to-install-with"
+description = "A small example package"
+keywords = [
+ "development",
+ "packaging",
+ "sample",
+ "setuptools",
+]
+license = { text = "mit" } # license = { file = "LICENSE.txt" }
+maintainers = [
+ { name = "Anirban Ray" },
+ { email = "39331844+yarnabrina@users.noreply.github.com" },
+ { name = "First Maintainer", email = "first.maintainer@example.com" },
+ { name = "Second Maintainer", email = "second.maintainer@example.com" },
+]
+authors = [
+ { name = "Anirban Ray" },
+ { email = "39331844+yarnabrina@users.noreply.github.com" },
+ { name = "First Author", email = "first.author@example.com" },
+ { name = "Second Author", email = "second.author@example.com" },
+]
+requires-python = ">=3.10"
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Framework :: Flake8",
+ "Framework :: Pytest",
+ "Framework :: Sphinx",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3 :: Only",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Topic :: Software Development",
+ "Topic :: Software Development :: Build Tools",
+ "Topic :: Software Development :: Libraries",
+ "Topic :: Utilities",
+ "Typing :: Typed",
+]
+dynamic=[
+ "readme",
+ "version",
+]
+dependencies = [
+ "PySimpleGUI",
+]
+[project.optional-dependencies]
+all = [
+ "autoflake",
+ "bandit",
+ "black",
+ "blacken-docs",
+ "build",
+ "codespell",
+ "coverage[toml]",
+ "docformatter[tomli]",
+ "flake8",
+ "furo",
+ "hypothesis[pytest]",
+ "interrogate",
+ "isort",
+ "mypy",
+ "nox",
+ "pre-commit",
+ "pydocstyle[toml]",
+ "pylint",
+ "pyproject-fmt",
+ "pyright",
+ "pytest",
+ "pyupgrade",
+ "Sphinx",
+ "sphinx-copybutton",
+ "twine",
+ "validate-pyproject",
+ "vulture",
+]
+dev = [
+ "codespell",
+ "nox",
+ "pre-commit",
+]
+doc = [
+ "furo",
+ "Sphinx",
+ "sphinx-copybutton",
+]
+format = [
+ "autoflake",
+ "black",
+ "blacken-docs",
+ "docformatter[tomli]",
+ "isort",
+ "pyproject-fmt",
+ "pyupgrade",
+]
+lint = [
+ "bandit",
+ "flake8",
+ "interrogate",
+ "mypy",
+ "pydocstyle[toml]",
+ "pylint",
+ "pyright",
+ "validate-pyproject",
+ "vulture",
+]
+release = [
+ "build",
+ "twine",
+]
+test = [
+ "coverage[toml]",
+ "hypothesis[pytest]",
+ "pytest",
+]
+[project.urls]
+"Bug Tracker" = "https://github.com/yarnabrina/learn-python-packaging/issues"
+"Documentation" = "https://learn-python-packaging.readthedocs.io"
+"Source Code" = "https://github.com/yarnabrina/learn-python-packaging"
+[project.scripts]
+console-calculator = "module_that_can_be_invoked_from_cli:console_calculator"
+[project.gui-scripts]
+gui-calculator = "module_that_can_invoke_gui_from_cli:gui_calculator"
+
+[tool.setuptools]
+py-modules = [
+ "module_that_can_be_imported_directly",
+ "module_that_can_be_invoked_from_cli",
+ "module_that_can_invoke_gui_from_cli",
+]
+
+[tool.setuptools.dynamic]
+version = { attr = "package_name_to_import_with.__version__" }
+readme = { file = "README.md", content-type = "text/markdown" }
+
+[tool.setuptools.packages.find]
+where = [
+ "src",
+]
+include = [
+ "package_name_to_import_with*",
+]
+exclude = [
+ "*tests*",
+]
+namespaces = false
+
+[tool.setuptools.package-data]
+"package_name_to_import_with" = [
+ "metadata.json",
+ "py.typed",
+]
+
+[tool.setuptools.exclude-package-data]
+"*" = [
+ ".gitattributes",
+ ".gitignore",
+]
+
+[tool.black]
+line-length = 99
+target-version = [
+ "py310",
+]
+safe = true
+
+[tool.isort]
+overwrite_in_place = true
+profile = "black"
+atomic = true
+float_to_top = true
+line_length = 99
+remove_redundant_aliases = true
+src_paths = "src"
+py_version = 310
+
+[tool.pytest.ini_options]
+addopts = "--junit-xml=pytest_junit_report.xml --doctest-modules --doctest-ignore-import-errors --doctest-continue-on-failure"
+console_output_style = "count"
+
+[tool.coverage.run]
+branch = true
+command_line = "--module pytest"
+data_file = "coverage_data"
+include = [
+ "src/**/*.py",
+]
+omit = [
+ "**/tests/*.py",
+]
+
+[tool.coverage.report]
+fail_under = 80
+include = [
+ "src/**/*.py",
+]
+omit = [
+ "src/module_that_can_invoke_gui_from_cli.py",
+ "**/tests/*.py",
+]
+precision = 2
+exclude_lines = [
+ "pragma: no cover",
+ "if __name__ == .__main__.:",
+ "if typing.TYPE_CHECKING:",
+]
+
+[tool.coverage.html]
+directory = "coverage_html_report"
+
+[tool.coverage.xml]
+output = "coverage_xml_report.xml"
+
+[tool.mypy]
+files = [
+ "src",
+]
+exclude = [
+ "conftest",
+ "test_",
+]
+ignore_missing_imports = true
+strict = true
+
+[tool.autoflake]
+in-place = true
+remove-all-unused-imports = true
+recursive = true
+expand-star-imports = true
+ignore-init-module-imports = true
+remove-duplicate-keys = true
+remove-unused-variables = true
+
+[tool.docformatter]
+in-place = true
+recursive = true
+wrap-summaries = 99
+wrap-descriptions = 99
+
+[tool.interrogate]
+fail-under = 80
+ignore-init-method = true
+
+[tool.pydocstyle]
+convention = "numpy"
+
+[tool.pylint.main]
+fail-under = 8.0
+jobs = 0
+recursive = true
+
+[tool.pylint.basic]
+include-naming-hint = true
+
+[tool.pylint.format]
+max-line-length = 99
+
+[tool.pylint.logging]
+logging-format-style = "new"
+
+[tool.pylint."messages control"]
+enable = [
+ "all",
+]
+
+[tool.pylint.reports]
+output-format = "colorized"
+
+[tool.pyright]
+include = [
+ "src",
+]
+exclude = [
+ "**/tests/*.py",
+]
+reportMissingImports = false
+
+[tool.ruff]
+extend-exclude = [
+ "*.pyi",
+]
+fix = true
+format = "grouped"
+ignore = [
+ "ANN401",
+ "D203",
+ "D213",
+ "TRY003",
+]
+ignore-init-module-imports = true
+line-length = 99
+select = [
+ "F",
+ "E",
+ "W",
+ "C90",
+ "I",
+ "N",
+ "D",
+ "UP",
+ "ANN",
+ "S",
+ "B",
+ "A",
+ "C4",
+ "T10",
+ "ISC",
+ "G",
+ "T20",
+ "PT",
+ "Q",
+ "SIM",
+ "TID",
+ "ERA",
+ "PD",
+ "PGH",
+ "PL",
+ "TRY",
+ "RUF",
+]
+src = [
+ "src",
+]
+target-version = "py310"
+
+[tool.ruff.per-file-ignores]
+"**/test_*.py" = [
+ "S101",
+]
+
+[tool.vulture]
+min_confidence = 100
+paths = [
+ "src",
+]
diff --git a/requirements/requirements.dev.txt b/requirements/requirements.dev.txt
new file mode 100644
index 0000000..73ccce6
--- /dev/null
+++ b/requirements/requirements.dev.txt
@@ -0,0 +1,3 @@
+codespell
+nox
+pre-commit
diff --git a/requirements/requirements.doc.txt b/requirements/requirements.doc.txt
new file mode 100644
index 0000000..bfb8339
--- /dev/null
+++ b/requirements/requirements.doc.txt
@@ -0,0 +1,3 @@
+furo
+Sphinx
+sphinx-copybutton
diff --git a/requirements/requirements.format.txt b/requirements/requirements.format.txt
new file mode 100644
index 0000000..b4a5500
--- /dev/null
+++ b/requirements/requirements.format.txt
@@ -0,0 +1,7 @@
+autoflake
+black
+blacken-docs
+docformatter[tomli]
+isort
+pyproject-fmt
+pyupgrade
diff --git a/requirements/requirements.lint.txt b/requirements/requirements.lint.txt
new file mode 100644
index 0000000..9936dd0
--- /dev/null
+++ b/requirements/requirements.lint.txt
@@ -0,0 +1,9 @@
+bandit
+flake8
+interrogate
+mypy
+pydocstyle
+pylint
+pyright
+validate-pyproject
+vulture
diff --git a/requirements/requirements.release.txt b/requirements/requirements.release.txt
new file mode 100644
index 0000000..e47b6e9
--- /dev/null
+++ b/requirements/requirements.release.txt
@@ -0,0 +1,2 @@
+build
+twine
diff --git a/requirements/requirements.test.txt b/requirements/requirements.test.txt
new file mode 100644
index 0000000..8b85357
--- /dev/null
+++ b/requirements/requirements.test.txt
@@ -0,0 +1,3 @@
+coverage[toml]
+hypothesis[pytest]
+pytest
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
new file mode 100644
index 0000000..4671555
--- /dev/null
+++ b/requirements/requirements.txt
@@ -0,0 +1 @@
+PySimpleGUI
diff --git a/src/module_that_can_be_imported_directly.py b/src/module_that_can_be_imported_directly.py
new file mode 100644
index 0000000..b3be013
--- /dev/null
+++ b/src/module_that_can_be_imported_directly.py
@@ -0,0 +1,4 @@
+"""Store package version."""
+from package_name_to_import_with import __version__
+
+VERSION: str = __version__
diff --git a/src/module_that_can_be_invoked_from_cli.py b/src/module_that_can_be_invoked_from_cli.py
new file mode 100644
index 0000000..1e9b825
--- /dev/null
+++ b/src/module_that_can_be_invoked_from_cli.py
@@ -0,0 +1,43 @@
+"""Calculate arithmetic expressions from command line."""
+import argparse
+import sys
+import typing
+
+import package_name_to_import_with
+
+
+def capture_user_inputs() -> dict[str, typing.Any]:
+ """Capture user inputs for arithmetic expression.
+
+ Returns
+ -------
+ dict[str, typing.Any]
+ captured user inputs
+ """
+ parser = argparse.ArgumentParser(description="calculator for console", add_help=True)
+
+ parser.add_argument("first_number", help="first number")
+ parser.add_argument("operator", help="arithmetic operator")
+ parser.add_argument("second_number", help="second number")
+
+ parsed_arguments, _ = parser.parse_known_args()
+
+ return vars(parsed_arguments)
+
+
+def console_calculator() -> None:
+ """Calculate arithmetic expressions."""
+ user_inputs = capture_user_inputs()
+
+ try:
+ operation_result = package_name_to_import_with.calculate_results(
+ user_inputs["first_number"], user_inputs["operator"], user_inputs["second_number"]
+ )
+ except Exception as error: # pylint: disable=broad-except
+ sys.stderr.write(f"Error: {error}")
+ else:
+ sys.stdout.write(f"Result = {operation_result}")
+
+
+if __name__ == "__main__":
+ console_calculator()
diff --git a/src/module_that_can_invoke_gui_from_cli.py b/src/module_that_can_invoke_gui_from_cli.py
new file mode 100644
index 0000000..00e0594
--- /dev/null
+++ b/src/module_that_can_invoke_gui_from_cli.py
@@ -0,0 +1,92 @@
+"""Calculate arithmetic expressions from GUI."""
+import PySimpleGUI
+
+import package_name_to_import_with
+
+FIRST_NUMBER_INPUT = "first_number"
+SECOND_NUMBER_INPUT = "second_number"
+OPERATOR_INPUT = "operator"
+OPERATION_RESULT = "result"
+CLOSE_BUTTON = "Close"
+
+
+def define_gui_layout() -> list[list[PySimpleGUI.Element]]:
+ """Prepare design of the GUI.
+
+ Returns
+ -------
+ list[list[PySimpleGUI.Element]]
+ elements of the GUI
+ """
+ layout = [
+ [PySimpleGUI.Text("Enter first number"), PySimpleGUI.Input(key=FIRST_NUMBER_INPUT)],
+ [
+ PySimpleGUI.Text("Enter operator"),
+ PySimpleGUI.OptionMenu(["+", "-", "*", "/"], key=OPERATOR_INPUT),
+ ],
+ [PySimpleGUI.Text("Enter second number"), PySimpleGUI.Input(key=SECOND_NUMBER_INPUT)],
+ [PySimpleGUI.Button(button_text="Submit")],
+ [PySimpleGUI.Text("Operation Result", key=OPERATION_RESULT)],
+ [PySimpleGUI.Button(button_text=CLOSE_BUTTON)],
+ ]
+
+ return layout
+
+
+def define_gui_window(gui_layout: list[list[PySimpleGUI.Element]]) -> PySimpleGUI.Window:
+ """Create GUI with provided design.
+
+ Parameters
+ ----------
+ gui_layout : list[list[PySimpleGUI.Element]]
+ design of the GUI
+
+ Returns
+ -------
+ PySimpleGUI.Window
+ designed GUI
+ """
+ window = PySimpleGUI.Window("GUI Calculator", layout=gui_layout)
+
+ return window
+
+
+def orchestrate_interaction(gui_window: PySimpleGUI.Window) -> None:
+ """Control flow of the GUI.
+
+ Parameters
+ ----------
+ gui_window : PySimpleGUI.Window
+ designed GUI
+ """
+ while True:
+ gui_event, gui_elements = gui_window.read()
+
+ if PySimpleGUI.WINDOW_CLOSED or gui_event == CLOSE_BUTTON:
+ break
+
+ try:
+ operation_result = package_name_to_import_with.calculate_results(
+ gui_elements[FIRST_NUMBER_INPUT],
+ gui_elements[OPERATOR_INPUT],
+ gui_elements[SECOND_NUMBER_INPUT],
+ )
+ except Exception as error: # pylint: disable=broad-except
+ gui_window[OPERATION_RESULT].update(value=str(error))
+ else:
+ gui_window[OPERATION_RESULT].update(value=operation_result)
+
+
+def gui_calculator() -> None:
+ """Calculate arithmetic expressions."""
+ gui_layout = define_gui_layout()
+ gui_window = define_gui_window(gui_layout)
+
+ try:
+ orchestrate_interaction(gui_window)
+ finally:
+ gui_window.close()
+
+
+if __name__ == "__main__":
+ gui_calculator()
diff --git a/src/package_name_to_import_with/__init__.py b/src/package_name_to_import_with/__init__.py
new file mode 100644
index 0000000..e4f2e73
--- /dev/null
+++ b/src/package_name_to_import_with/__init__.py
@@ -0,0 +1,7 @@
+"""Expose selected package contents."""
+from .calculator_sub_package import calculate_results
+from .data_using_module import METADATA
+from .garbage_collection_module import define_garbage_collection_decorator
+
+__all__ = ["calculate_results", "define_garbage_collection_decorator"]
+__version__: str = METADATA["Version"]
diff --git a/src/package_name_to_import_with/calculator_sub_package/__init__.py b/src/package_name_to_import_with/calculator_sub_package/__init__.py
new file mode 100644
index 0000000..8f662ee
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/__init__.py
@@ -0,0 +1,15 @@
+"""Expose selected sub-package contents."""
+from .inverses_module import get_negative, get_reciprocal
+from .operations_module import add_numbers, multiply_numbers
+from .utility_module import divide_numbers, subtract_numbers
+from .wrapper_module import calculate_results
+
+__all__ = [
+ "get_negative",
+ "get_reciprocal",
+ "add_numbers",
+ "multiply_numbers",
+ "divide_numbers",
+ "subtract_numbers",
+ "calculate_results",
+]
diff --git a/src/package_name_to_import_with/calculator_sub_package/inverses_module.py b/src/package_name_to_import_with/calculator_sub_package/inverses_module.py
new file mode 100644
index 0000000..f871497
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/inverses_module.py
@@ -0,0 +1,65 @@
+"""Define inverses."""
+
+
+def get_negative(input_number: float) -> float:
+ """Get additive inverse of a real number.
+
+ Parameters
+ ----------
+ input_number : float
+ value of number
+
+ Returns
+ -------
+ float
+ negative of ``input_number``
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with.calculator_sub_package import get_negative
+ >>> get_negative(1)
+ -1
+ >>> get_negative(-1)
+ 1
+ """
+ additive_inverse = (-1) * input_number
+
+ return additive_inverse
+
+
+def get_reciprocal(input_number: float) -> float:
+ """Get multiplicative inverse of a real number.
+
+ Parameters
+ ----------
+ input_number : float
+ value of number
+
+ Returns
+ -------
+ float
+ reciprocal of ``input_number``
+
+ Raises
+ ------
+ ValueError
+ if ``input_number`` is zero
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with.calculator_sub_package import get_reciprocal
+ >>> get_reciprocal(2)
+ 0.5
+ >>> get_reciprocal(0.5)
+ 2.0
+ """
+ try:
+ multiplicative_inverse = 1 / input_number
+ except ZeroDivisionError as error:
+ raise ValueError("Multiplicative inverse is not defined for zero") from error
+
+ return multiplicative_inverse
diff --git a/src/package_name_to_import_with/calculator_sub_package/operations_module.py b/src/package_name_to_import_with/calculator_sub_package/operations_module.py
new file mode 100644
index 0000000..571c91d
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/operations_module.py
@@ -0,0 +1,69 @@
+"""Define functions to add and multiply."""
+
+
+def add_numbers(first_number: float, second_number: float) -> float:
+ """Perform addition of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ sum of ``first_number`` and ``second_number``
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with.calculator_sub_package import add_numbers
+ >>> add_numbers(1, 2)
+ 3
+ >>> add_numbers(1, -2)
+ -1
+ >>> add_numbers(-1, 2)
+ 1
+ >>> add_numbers(-1, -2)
+ -3
+ """
+ sum_of_two_numbers = first_number + second_number
+
+ return sum_of_two_numbers
+
+
+def multiply_numbers(first_number: float, second_number: float) -> float:
+ """Perform multiplication of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ product of two ``first_number`` and ``second_number``
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with.calculator_sub_package import multiply_numbers
+ >>> multiply_numbers(1, 2)
+ 2
+ >>> multiply_numbers(1, -2)
+ -2
+ >>> multiply_numbers(-1, 2)
+ -2
+ >>> multiply_numbers(-1, -2)
+ 2
+ """
+ product_of_two_numbers = first_number * second_number
+
+ return product_of_two_numbers
diff --git a/src/package_name_to_import_with/calculator_sub_package/tests/__init__.py b/src/package_name_to_import_with/calculator_sub_package/tests/__init__.py
new file mode 100644
index 0000000..fa335fd
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/tests/__init__.py
@@ -0,0 +1 @@
+"""Define unit tests for package contents."""
diff --git a/src/package_name_to_import_with/calculator_sub_package/tests/conftest.py b/src/package_name_to_import_with/calculator_sub_package/tests/conftest.py
new file mode 100644
index 0000000..50952e5
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/tests/conftest.py
@@ -0,0 +1,209 @@
+"""Define common inputs for unit tests."""
+import typing
+
+import pytest
+
+from package_name_to_import_with.calculator_sub_package.wrapper_module import ArithmeticOperator
+
+
+@pytest.fixture(params=[4, -9, 1.02, -3.4], name="first_number")
+def fixture_first_number(request: pytest.FixtureRequest) -> float:
+ """Define first input for unit tests.
+
+ Parameters
+ ----------
+ request : pytest.FixtureRequest
+ request for fixture from test function
+
+ Returns
+ -------
+ float
+ value of first input
+ """
+ return request.param
+
+
+@pytest.fixture(params=[5, 10, -5.6, 7.89], name="second_number")
+def fixture_second_number(request: pytest.FixtureRequest) -> float:
+ """Define second input for unit tests.
+
+ Parameters
+ ----------
+ request : pytest.FixtureRequest
+ request for fixture from test function
+
+ Returns
+ -------
+ float
+ value of second input
+ """
+ return request.param
+
+
+@pytest.fixture(params=["+", "-", "*", "/"], name="operator")
+def fixture_operator(request: pytest.FixtureRequest) -> typing.Literal["+", "-", "*", "/"]:
+ """Define operator for unit tests.
+
+ Parameters
+ ----------
+ request : pytest.FixtureRequest
+ request for fixture from test function
+
+ Returns
+ -------
+ typing.Literal[ "+", "-", "*", "/" ]
+ value of operator
+ """
+ return request.param
+
+
+@pytest.fixture()
+def expected_sum(first_number: float, second_number: float) -> float:
+ """Define expected sum for unit tests.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ value of expected sum
+ """
+ return first_number + second_number
+
+
+@pytest.fixture()
+def expected_difference(first_number: float, second_number: float) -> float:
+ """Define expected difference for unit tests.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ value of expected difference
+ """
+ return first_number - second_number
+
+
+@pytest.fixture()
+def expected_product(first_number: float, second_number: float) -> float:
+ """Define expected product for unit tests.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ value of expected product
+ """
+ return first_number * second_number
+
+
+@pytest.fixture()
+def expected_quotient(first_number: float, second_number: float) -> float:
+ """Define expected quotient for unit tests.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ value of expected quotient
+ """
+ return first_number / second_number
+
+
+@pytest.fixture()
+def expected_negative(first_number: float) -> float:
+ """Define expected negative for unit tests.
+
+ Parameters
+ ----------
+ first_number : float
+ value of input number
+
+ Returns
+ -------
+ float
+ value of expected negative
+ """
+ return (-1) * first_number
+
+
+@pytest.fixture()
+def expected_reciprocal(second_number: float) -> float:
+ """Define expected reciprocal for unit tests.
+
+ Parameters
+ ----------
+ second_number : float
+ value of input number
+
+ Returns
+ -------
+ float
+ value of expected reciprocal
+ """
+ return 1 / second_number
+
+
+@pytest.fixture()
+def expected_result(
+ first_number: float,
+ second_number: float,
+ operator: typing.Literal["+", "-", "*", "/"],
+) -> float:
+ """Define expected result for unit tests.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ operator : typing.Literal[ "+", "-", "*", "/" ]
+ type of arithmetic operation
+
+ Returns
+ -------
+ float
+ value of expected result
+
+ Raises
+ ------
+ ValueError
+ if ``operator`` not one of ``+``, ``-``, ``*``, ``/``
+ """
+ if ArithmeticOperator(operator) == ArithmeticOperator.ADDITION:
+ return first_number + second_number
+
+ if ArithmeticOperator(operator) == ArithmeticOperator.SUBTRACTION:
+ return first_number - second_number
+
+ if ArithmeticOperator(operator) == ArithmeticOperator.MULTIPLICATION:
+ return first_number * second_number
+
+ if ArithmeticOperator(operator) == ArithmeticOperator.DIVISION:
+ return first_number / second_number
+
+ raise ValueError("Unexpected value of operation")
diff --git a/src/package_name_to_import_with/calculator_sub_package/tests/test_hypotheses.py b/src/package_name_to_import_with/calculator_sub_package/tests/test_hypotheses.py
new file mode 100644
index 0000000..1e63aba
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/tests/test_hypotheses.py
@@ -0,0 +1,193 @@
+"""Define property based unit tests based on random data."""
+import enum
+import math
+import sys
+import typing
+
+import hypothesis
+import hypothesis.strategies
+
+from package_name_to_import_with.calculator_sub_package import (
+ add_numbers,
+ calculate_results,
+ divide_numbers,
+ get_negative,
+ get_reciprocal,
+ multiply_numbers,
+ subtract_numbers,
+)
+from package_name_to_import_with.calculator_sub_package.wrapper_module import ArithmeticOperator
+
+
+@enum.unique
+class InverseElements(enum.Enum):
+ """Define supported inverse elements."""
+
+ ADDITIVE_INVERSE = 0
+ MULTIPLICATIVE_INVERSE = 1
+
+
+def generate_finite_numbers() -> hypothesis.strategies.SearchStrategy:
+ """Generate real numbers which are neither infinity nor NaN.
+
+ Returns
+ -------
+ hypothesis.strategies.SearchStrategy
+ updated strategy
+ """
+ generate_numbers_strategy = hypothesis.strategies.one_of(
+ hypothesis.strategies.integers(),
+ hypothesis.strategies.floats(allow_nan=False, allow_infinity=False, allow_subnormal=False),
+ hypothesis.strategies.fractions(),
+ )
+
+ return generate_numbers_strategy
+
+
+@hypothesis.given(first_number=generate_finite_numbers(), second_number=generate_finite_numbers())
+def test_addition_hypothesis(first_number: float, second_number: float) -> None:
+ """Check addition of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ """
+ calculated_sum = add_numbers(first_number, second_number)
+ expected_sum = first_number + second_number
+
+ assert math.isclose(calculated_sum, expected_sum)
+
+
+@hypothesis.given(first_number=generate_finite_numbers(), second_number=generate_finite_numbers())
+def test_multiplication_hypothesis(first_number: float, second_number: float) -> None:
+ """Check multiplication of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ """
+ calculated_product = add_numbers(first_number, second_number)
+ expected_product = first_number + second_number
+
+ assert math.isclose(calculated_product, expected_product)
+
+
+@hypothesis.given(first_number=generate_finite_numbers())
+def test_additive_inverse_hypothesis(first_number: float) -> None:
+ """Check additive inverse of a real number.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ """
+ calculated_negative = get_negative(first_number)
+ negative_definition = add_numbers(first_number, calculated_negative)
+
+ assert math.isclose(negative_definition, InverseElements.ADDITIVE_INVERSE.value)
+
+
+@hypothesis.given(second_number=generate_finite_numbers())
+def test_multiplicative_inverse_hypothesis(second_number: float) -> None:
+ """Check multiplicative inverse of a real number.
+
+ Parameters
+ ----------
+ second_number : float
+ value of first number
+ """
+ hypothesis.assume(abs(second_number) > sys.float_info.epsilon)
+
+ calculated_reciprocal = get_reciprocal(second_number)
+ reciprocal_definition = multiply_numbers(second_number, calculated_reciprocal)
+
+ assert math.isclose(reciprocal_definition, InverseElements.MULTIPLICATIVE_INVERSE.value)
+
+
+@hypothesis.given(first_number=generate_finite_numbers(), second_number=generate_finite_numbers())
+def test_subtraction_hypothesis(first_number: float, second_number: float) -> None:
+ """Check subtraction of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ """
+ calculated_difference = subtract_numbers(first_number, second_number)
+ expected_difference = first_number - second_number
+
+ assert math.isclose(calculated_difference, expected_difference)
+
+
+@hypothesis.given(first_number=generate_finite_numbers(), second_number=generate_finite_numbers())
+def test_division_hypothesis(first_number: float, second_number: float) -> None:
+ """Check division of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ """
+ hypothesis.assume(abs(second_number) > sys.float_info.epsilon)
+
+ calculated_quotient = divide_numbers(first_number, second_number)
+ expected_quotient = first_number / second_number
+
+ assert math.isclose(calculated_quotient, expected_quotient)
+
+
+@hypothesis.given(
+ first_number=generate_finite_numbers(),
+ operator=hypothesis.strategies.sampled_from(ArithmeticOperator),
+ second_number=generate_finite_numbers(),
+)
+def test_operation_hypothesis(
+ first_number: float, operator: typing.Literal["+", "-", "*", "/"], second_number: float
+) -> None:
+ """Check operation of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ operator : typing.Literal[ "+", "-", "*", "/" ]
+ type of arithmetic operation
+ second_number : float
+ value of second number
+
+ Raises
+ ------
+ ValueError
+ if ``operator`` not one of ``+``, ``-``, ``*``, ``/``
+ """
+ hypothesis.assume(
+ not (
+ operator == ArithmeticOperator.DIVISION
+ and abs(second_number) <= sys.float_info.epsilon
+ )
+ )
+
+ calculated_result = calculate_results(first_number, operator, second_number)
+
+ if ArithmeticOperator(operator) == ArithmeticOperator.ADDITION:
+ expected_result = first_number + second_number
+ elif ArithmeticOperator(operator) == ArithmeticOperator.SUBTRACTION:
+ expected_result = first_number - second_number
+ elif ArithmeticOperator(operator) == ArithmeticOperator.MULTIPLICATION:
+ expected_result = first_number * second_number
+ elif ArithmeticOperator(operator) == ArithmeticOperator.DIVISION:
+ expected_result = first_number / second_number
+ else:
+ raise ValueError("Unexpected value of operation")
+
+ assert math.isclose(calculated_result, expected_result)
diff --git a/src/package_name_to_import_with/calculator_sub_package/tests/test_inverses.py b/src/package_name_to_import_with/calculator_sub_package/tests/test_inverses.py
new file mode 100644
index 0000000..5d1b443
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/tests/test_inverses.py
@@ -0,0 +1,42 @@
+"""Define unit tests for arithmetic operations."""
+import math
+
+import pytest
+
+from package_name_to_import_with import calculator_sub_package
+
+
+def test_successful_additive_inverse(first_number: float, expected_negative: float) -> None:
+ """Check additive inverse of a real number.
+
+ Parameters
+ ----------
+ first_number : float
+ value of input number
+ expected_negative : float
+ value of expected negative
+ """
+ result = calculator_sub_package.get_negative(first_number)
+ assert math.isclose(result, expected_negative) # nosec B101
+
+
+def test_successful_multiplicative_inverse(
+ second_number: float, expected_reciprocal: float
+) -> None:
+ """Check multiplicative inverse of a real number.
+
+ Parameters
+ ----------
+ second_number : float
+ value of input number
+ expected_reciprocal : float
+ value of expected reciprocal
+ """
+ result = calculator_sub_package.get_reciprocal(second_number)
+ assert math.isclose(result, expected_reciprocal) # nosec B101
+
+
+def test_multiplicative_inverse_failure() -> None:
+ """Check failure because of zero-division."""
+ with pytest.raises(ValueError, match="Multiplicative inverse is not defined for zero"):
+ calculator_sub_package.get_reciprocal(0)
diff --git a/src/package_name_to_import_with/calculator_sub_package/tests/test_operations.py b/src/package_name_to_import_with/calculator_sub_package/tests/test_operations.py
new file mode 100644
index 0000000..1c3f421
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/tests/test_operations.py
@@ -0,0 +1,76 @@
+"""Define unit tests for arithmetic operations."""
+import math
+
+from package_name_to_import_with import calculator_sub_package
+
+
+def test_successful_addition(
+ first_number: float, second_number: float, expected_sum: float
+) -> None:
+ """Check addition of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ expected_sum : float
+ value of expected sum
+ """
+ result = calculator_sub_package.add_numbers(first_number, second_number)
+ assert math.isclose(result, expected_sum) # nosec B101
+
+
+def test_successful_subtraction(
+ first_number: float, second_number: float, expected_difference: float
+) -> None:
+ """Check subtraction of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ expected_difference : float
+ value of expected sum
+ """
+ result = calculator_sub_package.subtract_numbers(first_number, second_number)
+ assert math.isclose(result, expected_difference) # nosec B101
+
+
+def test_successful_multiplication(
+ first_number: float, second_number: float, expected_product: float
+) -> None:
+ """Check multiplication of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ expected_product : float
+ value of expected sum
+ """
+ result = calculator_sub_package.multiply_numbers(first_number, second_number)
+ assert math.isclose(result, expected_product) # nosec B101
+
+
+def test_successful_division(
+ first_number: float, second_number: float, expected_quotient: float
+) -> None:
+ """Check division of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+ expected_quotient : float
+ value of expected sum
+ """
+ result = calculator_sub_package.divide_numbers(first_number, second_number)
+ assert math.isclose(result, expected_quotient) # nosec B101
diff --git a/src/package_name_to_import_with/calculator_sub_package/tests/test_wrappers.py b/src/package_name_to_import_with/calculator_sub_package/tests/test_wrappers.py
new file mode 100644
index 0000000..09663d4
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/tests/test_wrappers.py
@@ -0,0 +1,64 @@
+"""Define unit tests for calculators."""
+import math
+import typing
+
+import pytest
+
+import package_name_to_import_with
+
+
+def test_successful_operation(
+ first_number: float,
+ operator: typing.Literal["+", "-", "*", "/"],
+ second_number: float,
+ expected_result: float,
+) -> None:
+ """Check operation of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ operator : typing.Literal[ "+", "-", "*", "/" ]
+ type of arithmetic operation
+ second_number : float
+ value of second number
+ expected_result : float
+ value of expected sum
+ """
+ result = package_name_to_import_with.calculate_results(first_number, operator, second_number)
+ assert math.isclose(result, expected_result) # nosec B101
+
+
+@pytest.mark.parametrize(
+ ("first_input", "operator", "second_input", "error"),
+ [
+ ("not_number", "+", 3, "Supports only real numbers"),
+ ("not_number", "-", 6, "Supports only real numbers"),
+ ("not_number", "*", 7, "Supports only real numbers"),
+ ("not_number", "/", 8, "Supports only real numbers"),
+ (3, "+", "not_number", "Supports only real numbers"),
+ (6, "-", "not_number", "Supports only real numbers"),
+ (7, "*", "not_number", "Supports only real numbers"),
+ (8, "/", "not_number", "Supports only real numbers"),
+ (0, "^", 0, "Supports only basic arithmetic"),
+ ],
+)
+def test_operation_failure(
+ first_input: typing.Any, operator: typing.Any, second_input: typing.Any, error: str
+) -> None:
+ """Check failure during calculations.
+
+ Parameters
+ ----------
+ first_input : typing.Any
+ input for first number
+ operator : typing.Any
+ input for operator
+ second_input : typing.Any
+ input for second number
+ error : str
+ expected error message
+ """
+ with pytest.raises(ValueError, match=error):
+ package_name_to_import_with.calculate_results(first_input, operator, second_input)
diff --git a/src/package_name_to_import_with/calculator_sub_package/utility_module.py b/src/package_name_to_import_with/calculator_sub_package/utility_module.py
new file mode 100644
index 0000000..f28a7bf
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/utility_module.py
@@ -0,0 +1,71 @@
+"""Define functions to subtract and divide."""
+from .inverses_module import get_negative, get_reciprocal
+from .operations_module import add_numbers, multiply_numbers
+
+
+def subtract_numbers(first_number: float, second_number: float) -> float:
+ """Perform subtraction of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ difference of ``first_number`` from ``second_number``
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with.calculator_sub_package import subtract_numbers
+ >>> subtract_numbers(1, 2)
+ -1
+ >>> subtract_numbers(1, -2)
+ 3
+ >>> subtract_numbers(-1, 2)
+ -3
+ >>> subtract_numbers(-1, -2)
+ 1
+ """
+ difference_of_two_numbers = add_numbers(first_number, get_negative(second_number))
+
+ return difference_of_two_numbers
+
+
+def divide_numbers(first_number: float, second_number: float) -> float:
+ """Perform division of two real numbers.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ second_number : float
+ value of second number
+
+ Returns
+ -------
+ float
+ quotient of ``first_number`` by ``second_number``
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with.calculator_sub_package import divide_numbers
+ >>> divide_numbers(1, 2)
+ 0.5
+ >>> divide_numbers(1, -2)
+ -0.5
+ >>> divide_numbers(-1, 2)
+ -0.5
+ >>> divide_numbers(-1, -2)
+ 0.5
+ """
+ quotient_of_two_numbers = multiply_numbers(first_number, get_reciprocal(second_number))
+
+ return quotient_of_two_numbers
diff --git a/src/package_name_to_import_with/calculator_sub_package/wrapper_module.py b/src/package_name_to_import_with/calculator_sub_package/wrapper_module.py
new file mode 100644
index 0000000..7d44f0b
--- /dev/null
+++ b/src/package_name_to_import_with/calculator_sub_package/wrapper_module.py
@@ -0,0 +1,183 @@
+"""Define user level functions."""
+import collections.abc
+import dataclasses
+import enum
+import typing
+
+from .operations_module import add_numbers, multiply_numbers
+from .utility_module import divide_numbers, subtract_numbers
+
+
+@enum.unique
+class ArithmeticOperator(enum.Enum):
+ """Define supported arithmetic operators."""
+
+ ADDITION = "+"
+ SUBTRACTION = "-"
+ MULTIPLICATION = "*"
+ DIVISION = "/"
+
+
+@dataclasses.dataclass
+class ArithmeticOperation:
+ """Define arithmetic operation.
+
+ Parameters
+ ----------
+ first_number : float
+ value of first number
+ operation : collections.abc.Callable[[float, float], float]
+ type of arithmetic operation
+ second_number : float
+ value of second number
+ """
+
+ first_number: float
+ operation: collections.abc.Callable[[float, float], float]
+ second_number: float
+
+ @property
+ def result(self: "ArithmeticOperation") -> float:
+ """Store result of arithmetic operation.
+
+ Returns
+ -------
+ float
+ result of arithmetic operation
+ """
+ return self.operation(self.first_number, self.second_number)
+
+
+def validate_number_input(user_input: typing.Any) -> float:
+ """Validate input is a number or attempt to convert.
+
+ Parameters
+ ----------
+ user_input : typing.Any
+ value of input
+
+ Returns
+ -------
+ float
+ number input
+
+ Raises
+ ------
+ ValueError
+ if input is not of number type
+ """
+ if isinstance(user_input, float):
+ return user_input
+
+ try:
+ converted_user_input = float(user_input)
+ except ValueError as error:
+ raise ValueError("Supports only real numbers") from error
+
+ return converted_user_input
+
+
+def validate_operator_input(
+ user_input: typing.Any,
+) -> collections.abc.Callable[[float, float], float]:
+ """Validate input is in {``+``, ``-``, ``*``, ``/``} and return corresponding operation.
+
+ Parameters
+ ----------
+ user_input : typing.Any
+ value of input
+
+ Returns
+ -------
+ collections.abc.Callable[[float, float], float]
+ operation function
+
+ Raises
+ ------
+ ValueError
+ if input is not a basic arithmetic operator
+ """
+ try:
+ processed_user_input = ArithmeticOperator(user_input)
+ except ValueError as error:
+ raise ValueError("Supports only basic arithmetic") from error
+
+ if processed_user_input == ArithmeticOperator.ADDITION:
+ return add_numbers
+
+ if processed_user_input == ArithmeticOperator.SUBTRACTION:
+ return subtract_numbers
+
+ if processed_user_input == ArithmeticOperator.MULTIPLICATION:
+ return multiply_numbers
+
+ if processed_user_input == ArithmeticOperator.DIVISION:
+ return divide_numbers
+
+ raise ValueError("Unexpected value of operation") # pragma: no cover
+
+
+def process_inputs(
+ first_input: typing.Any, operator: typing.Any, second_input: typing.Any
+) -> ArithmeticOperation:
+ """Validate and convert user inputs.
+
+ Parameters
+ ----------
+ first_input : typing.Any
+ input for first number
+ operator : typing.Any
+ input for arithmetic operator
+ second_input : typing.Any
+ input for second number
+
+ Returns
+ -------
+ ArithmeticOperation
+ validated expression
+ """
+ first_number = validate_number_input(first_input)
+ operator = validate_operator_input(operator)
+ second_number = validate_number_input(second_input)
+
+ return ArithmeticOperation(first_number, operator, second_number)
+
+
+def calculate_results(
+ first_input: typing.Any,
+ operator: typing.Literal["+", "-", "*", "/"],
+ second_input: typing.Any,
+) -> float:
+ """Perform basic arithmetic operations.
+
+ Parameters
+ ----------
+ first_input : typing.Any
+ value of first number
+ operator : typing.Literal[ "+", "-", "*", "/" ]
+ type of arithmetic operation
+ second_input : typing.Any
+ value of second number
+
+ Returns
+ -------
+ float
+ result of arithmetic operation
+
+ Examples
+ --------
+ .. code-block:: pycon
+
+ >>> from package_name_to_import_with import calculate_results
+ >>> calculate_results(1, "+", 2)
+ 3.0
+ >>> calculate_results(1, "-", 2)
+ -1.0
+ >>> calculate_results(1, "*", 2)
+ 2.0
+ >>> calculate_results(1, "/", 2)
+ 0.5
+ """
+ arithmetic_expression = process_inputs(first_input, operator, second_input)
+
+ return arithmetic_expression.result
diff --git a/src/package_name_to_import_with/data_using_module.py b/src/package_name_to_import_with/data_using_module.py
new file mode 100644
index 0000000..14a83ed
--- /dev/null
+++ b/src/package_name_to_import_with/data_using_module.py
@@ -0,0 +1,17 @@
+"""Define package contents."""
+import importlib.resources
+import json
+import typing
+
+
+class PackageMetadata(typing.TypedDict):
+ """Define keys and types of corresponding values for package metadata."""
+
+ Name: str
+ Version: str
+
+
+METADATA_CONTENTS: str = (
+ importlib.resources.files("package_name_to_import_with").joinpath("metadata.json").read_text()
+)
+METADATA: PackageMetadata = json.loads(METADATA_CONTENTS)
diff --git a/src/package_name_to_import_with/garbage_collection_module.py b/src/package_name_to_import_with/garbage_collection_module.py
new file mode 100644
index 0000000..a5f966a
--- /dev/null
+++ b/src/package_name_to_import_with/garbage_collection_module.py
@@ -0,0 +1,45 @@
+"""Define package contents."""
+import collections.abc
+import functools
+import gc
+import typing
+
+
+def define_garbage_collection_decorator(
+ function_to_be_decorated: collections.abc.Callable[..., typing.Any]
+) -> "collections.abc.Callable[..., typing.Any]": # pragma: no cover
+ """Perform forcefully garbage collection after execution of provided function.
+
+ Parameters
+ ----------
+ function_to_be_decorated : collections.abc.Callable[..., typing.Any]
+ function whose execution may require forceful garbage collection
+
+ Returns
+ -------
+ collections.abc.Callable[..., typing.Any]
+ decorated function
+ """
+
+ @functools.wraps(function_to_be_decorated)
+ def wrapper_function(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
+ """Execute provided function with forceful garbage collection afterwards.
+
+ Parameters
+ ----------
+ args : tuple
+ positional arguments for ``function_to_be_decorated``
+ kwargs : dict
+ keyword arguments for ``function_to_be_decorated``
+
+ Returns
+ -------
+ typing.Any
+ output of the provided function with provided arguments
+ """
+ result = function_to_be_decorated(*args, **kwargs)
+ _ = gc.collect()
+
+ return result
+
+ return wrapper_function
diff --git a/src/package_name_to_import_with/metadata.json b/src/package_name_to_import_with/metadata.json
new file mode 100644
index 0000000..aff3ce5
--- /dev/null
+++ b/src/package_name_to_import_with/metadata.json
@@ -0,0 +1,4 @@
+{
+ "Name": "package-name-to-install-with",
+ "Version": "0.0.2"
+}
diff --git a/src/package_name_to_import_with/py.typed b/src/package_name_to_import_with/py.typed
new file mode 100644
index 0000000..e69de29
diff --git a/src/tests/__init__.py b/src/tests/__init__.py
new file mode 100644
index 0000000..fa335fd
--- /dev/null
+++ b/src/tests/__init__.py
@@ -0,0 +1 @@
+"""Define unit tests for package contents."""
diff --git a/src/tests/test_cli.py b/src/tests/test_cli.py
new file mode 100644
index 0000000..9f6b11b
--- /dev/null
+++ b/src/tests/test_cli.py
@@ -0,0 +1,112 @@
+"""Define unit tests for console calculator."""
+import re
+import unittest.mock
+
+import pytest
+
+import module_that_can_be_invoked_from_cli
+
+
+def test_sum(capsys: pytest.CaptureFixture) -> None:
+ """Check addition of two numbers.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "4", "+", "5"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ sum_result, _ = capsys.readouterr()
+
+ assert re.match("Result = 9.0", sum_result) # nosec B101
+
+
+def test_difference(capsys: pytest.CaptureFixture) -> None:
+ """Check subtraction of two numbers.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "8", "-", "7"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ difference_result, _ = capsys.readouterr()
+
+ assert re.match("Result = 1.0", difference_result) # nosec B101
+
+
+def test_product(capsys: pytest.CaptureFixture) -> None:
+ """Check multiplication of two numbers.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "2", "*", "3"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ product_result, _ = capsys.readouterr()
+
+ assert re.match("Result = 6.0", product_result) # nosec B101
+
+
+def test_quotient(capsys: pytest.CaptureFixture) -> None:
+ """Check division of two numbers.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "0", "/", "10"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ quotient_result, _ = capsys.readouterr()
+
+ assert re.match("Result = 0.0", quotient_result) # nosec B101
+
+
+def test_first_input_failure(capsys: pytest.CaptureFixture) -> None:
+ """Check failure in first user input.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "one", "+", "1"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ _, result_error = capsys.readouterr()
+
+ assert re.match("Error: Supports only real numbers", result_error) # nosec B101
+
+
+def test_second_input_failure(capsys: pytest.CaptureFixture) -> None:
+ """Check failure in second user input.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "2", "*", "two"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ _, result_error = capsys.readouterr()
+
+ assert re.match("Error: Supports only real numbers", result_error) # nosec B101
+
+
+def test_operator_input_failure(capsys: pytest.CaptureFixture) -> None:
+ """Check failure in operator input.
+
+ Parameters
+ ----------
+ capsys : pytest.CaptureFixture
+ fixture capturing ``sys.stdout`` and ``sys.stderr``
+ """
+ with unittest.mock.patch("sys.argv", ["prog", "0", "x", "0"]):
+ module_that_can_be_invoked_from_cli.console_calculator()
+ _, result_error = capsys.readouterr()
+
+ assert re.match("Error: Supports only basic arithmetic", result_error) # nosec B101
diff --git a/src/tests/test_version.py b/src/tests/test_version.py
new file mode 100644
index 0000000..3f8088e
--- /dev/null
+++ b/src/tests/test_version.py
@@ -0,0 +1,11 @@
+"""Define unit tests for version."""
+import module_that_can_be_imported_directly
+import package_name_to_import_with
+
+
+def test_package_version() -> None:
+ """Check version of package and exposed module."""
+ package_version = package_name_to_import_with.__version__
+ module_version = module_that_can_be_imported_directly.VERSION
+
+ assert package_version == module_version # nosec B101
diff --git a/typing-stubs-for-package-name-to-install-with/module_that_can_be_imported_directly.pyi b/typing-stubs-for-package-name-to-install-with/module_that_can_be_imported_directly.pyi
new file mode 100644
index 0000000..3acee93
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/module_that_can_be_imported_directly.pyi
@@ -0,0 +1 @@
+VERSION: str
diff --git a/typing-stubs-for-package-name-to-install-with/module_that_can_be_invoked_from_cli.pyi b/typing-stubs-for-package-name-to-install-with/module_that_can_be_invoked_from_cli.pyi
new file mode 100644
index 0000000..0da9dcc
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/module_that_can_be_invoked_from_cli.pyi
@@ -0,0 +1,4 @@
+import typing
+
+def capture_user_inputs() -> dict[str, typing.Any]: ...
+def console_calculator() -> None: ...
diff --git a/typing-stubs-for-package-name-to-install-with/module_that_can_invoke_gui_from_cli.pyi b/typing-stubs-for-package-name-to-install-with/module_that_can_invoke_gui_from_cli.pyi
new file mode 100644
index 0000000..cf09f1c
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/module_that_can_invoke_gui_from_cli.pyi
@@ -0,0 +1,12 @@
+import PySimpleGUI
+
+FIRST_NUMBER_INPUT: str
+SECOND_NUMBER_INPUT: str
+OPERATOR_INPUT: str
+OPERATION_RESULT: str
+CLOSE_BUTTON: str
+
+def define_gui_layout() -> list[list[PySimpleGUI.Element]]: ...
+def define_gui_window(gui_layout: list[list[PySimpleGUI.Element]]) -> PySimpleGUI.Window: ...
+def orchestrate_interaction(gui_window: PySimpleGUI.Window) -> None: ...
+def gui_calculator() -> None: ...
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/__init__.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/__init__.pyi
new file mode 100644
index 0000000..2ef15c1
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/__init__.pyi
@@ -0,0 +1,2 @@
+from .calculator_sub_package import calculate_results
+from .garbage_collection_module import define_garbage_collection_decorator
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/__init__.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/__init__.pyi
new file mode 100644
index 0000000..4ab95a8
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/__init__.pyi
@@ -0,0 +1,4 @@
+from .inverses_module import get_negative, get_reciprocal
+from .operations_module import add_numbers, multiply_numbers
+from .utility_module import divide_numbers, subtract_numbers
+from .wrapper_module import calculate_results
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/inverses_module.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/inverses_module.pyi
new file mode 100644
index 0000000..22f1548
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/inverses_module.pyi
@@ -0,0 +1,2 @@
+def get_negative(input_number: float) -> float: ...
+def get_reciprocal(input_number: float) -> float: ...
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/operations_module.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/operations_module.pyi
new file mode 100644
index 0000000..701a4d0
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/operations_module.pyi
@@ -0,0 +1,2 @@
+def add_numbers(first_number: float, second_number: float) -> float: ...
+def multiply_numbers(first_number: float, second_number: float) -> float: ...
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/utility_module.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/utility_module.pyi
new file mode 100644
index 0000000..c057e29
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/utility_module.pyi
@@ -0,0 +1,5 @@
+from .inverses_module import get_negative, get_reciprocal
+from .operations_module import add_numbers, multiply_numbers
+
+def subtract_numbers(first_number: float, second_number: float) -> float: ...
+def divide_numbers(first_number: float, second_number: float) -> float: ...
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/wrapper_module.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/wrapper_module.pyi
new file mode 100644
index 0000000..0c641cd
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/calculator_sub_package/wrapper_module.pyi
@@ -0,0 +1,31 @@
+import collections.abc
+import enum
+import typing
+
+from .operations_module import add_numbers, multiply_numbers
+from .utility_module import divide_numbers, subtract_numbers
+
+class ArithmeticOperator(enum.Enum):
+ ADDITION: str
+ SUBTRACTION: str
+ MULTIPLICATION: str
+ DIVISION: str
+
+class ArithmeticOperation:
+ first_number: float
+ operation: collections.abc.Callable[[float, float], float]
+ second_number: float
+ @property
+ def result(self) -> float: ...
+ def __init__(self, first_number, operation, second_number) -> None: ...
+
+def validate_number_input(user_input: typing.Any) -> float: ...
+def validate_operator_input(
+ user_input: typing.Any,
+) -> collections.abc.Callable[[float, float], float]: ...
+def process_inputs(
+ first_input: typing.Any, operator: typing.Any, second_input: typing.Any
+) -> ArithmeticOperation: ...
+def calculate_results(
+ first_input: typing.Any, operator: typing.Literal["+", "-", "*", "/"], second_input: typing.Any
+) -> float: ...
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/data_using_module.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/data_using_module.pyi
new file mode 100644
index 0000000..e8ebb8e
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/data_using_module.pyi
@@ -0,0 +1,8 @@
+import typing
+
+class PackageMetadata(typing.TypedDict):
+ Name: str
+ Version: str
+
+METADATA_CONTENTS: str
+METADATA: PackageMetadata
diff --git a/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/garbage_collection_module.pyi b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/garbage_collection_module.pyi
new file mode 100644
index 0000000..9b3ff06
--- /dev/null
+++ b/typing-stubs-for-package-name-to-install-with/package_name_to_import_with/garbage_collection_module.pyi
@@ -0,0 +1,6 @@
+import collections.abc
+import typing
+
+def define_garbage_collection_decorator(
+ function_to_be_decorated: collections.abc.Callable[..., typing.Any]
+) -> collections.abc.Callable[..., typing.Any]: ...