diff --git a/.github/FUNDING.yml.jinja b/.github/FUNDING.yml.jinja new file mode 100644 index 00000000..d354a8b1 --- /dev/null +++ b/.github/FUNDING.yml.jinja @@ -0,0 +1 @@ +github: [{{ repo_namespace }}] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 04695700..0b7a8901 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: cache: "pip" - run: make dev-lint - run: make lint - test: + tests: runs-on: ubuntu-latest strategy: matrix: @@ -32,8 +32,8 @@ jobs: with: python-version: ${{ matrix.python-version }} cache: "pip" - - run: make dev-test - - run: make test + - run: make dev-tests + - run: make tests - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 if: matrix.python-version == '3.8' diff --git a/.github/workflows/readthedocs-preview.yml.jinja b/.github/workflows/readthedocs-preview.yml.jinja new file mode 100644 index 00000000..8f905414 --- /dev/null +++ b/.github/workflows/readthedocs-preview.yml.jinja @@ -0,0 +1,17 @@ +name: Read the Docs Pull Request Preview + +on: + pull_request_target: + types: + - opened + +permissions: + pull-requests: write + +jobs: + documentation-links: + runs-on: ubuntu-latest + steps: + - uses: readthedocs/actions/preview@v1 + with: + project-slug: "{{ repo_name }}" diff --git a/.gitignore b/.gitignore index ca061553..cecba5bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Custom *.swp .DS_Store +.copier-answers.yml Pipfile # Byte-compiled / optimized / DLL files diff --git a/.gitignore.jinja b/.gitignore.jinja new file mode 100644 index 00000000..d9a1adf1 --- /dev/null +++ b/.gitignore.jinja @@ -0,0 +1,137 @@ +# Custom +*.swp +.DS_Store +{%- if project_name == "Serious Scaffold Python" %} +.copier-answers.yml +{%- endif %} +Pipfile + +# 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/ +pip-wheel-metadata/ +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/ + +# 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 +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.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 + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__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/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27f118e5..1af0a0d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: requirements/docs.txt, requirements/lint.txt, requirements/package.txt, - requirements/test.txt, + requirements/tests.txt, ] - id: sort-simple-yaml files: .pre-commit-config.yaml @@ -43,7 +43,7 @@ repos: entry: pipenv run python -m isort require_serial: true types_or: [cython, pyi, python] - args: ['--filter-files'] + args: ["--filter-files"] - id: mypy name: mypy language: system @@ -61,3 +61,8 @@ repos: language: system entry: pipenv run toml-sort -a -i types: [toml] + - id: forbidden-files + name: forbidden files + entry: found Copier update rejection files; review them and remove them + language: fail + files: "\\.rej$" diff --git a/.vscode/settings.json b/.vscode/settings.json index 2bb9879b..6bd8caa6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -15,6 +15,7 @@ "autofix", "automodule", "codecov", + "huxuan", "isort", "mypy", "pipenv", diff --git a/Makefile b/Makefile index 3af4dcac..b70c8182 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: clean deepclean install dev version pre-commit lint black mypy ruff toml-sort test freeze build upload docs docs-autobuild +.PHONY: clean deepclean install dev version pre-commit lint black mypy ruff toml-sort tests freeze build upload docs docs-autobuild # Construct pipenv run command with or without site-packages flag when not in CI environment and pipenv command exists. SITE_PACKAGES_FLAG = $(shell [ "${SS_SITE_PACKAGES}" = "true" ] && echo --site-packages) @@ -7,7 +7,7 @@ PIPRUN := $(shell [ "${CI}" != "true" ] && command -v pipenv > /dev/null 2>&1 && # Remove common intermediate files. clean: -rm -rf \ - *.egg-info \ + .copier-answers.yml \ .coverage \ .mypy_cache \ .pytest_cache \ @@ -15,10 +15,11 @@ clean: Pipfile* \ coverage.xml \ dist \ - docs\_build + docs/_build + find . -name '*.egg-info' -print0 | xargs -0 rm -rf find . -name '*.pyc' -print0 | xargs -0 rm -f find . -name '*.swp' -print0 | xargs -0 rm -f - find . -name '.DS_Store' -print0 | xargs -0 rm -rf + find . -name '.DS_Store' -print0 | xargs -0 rm -r find . -name '__pycache__' -print0 | xargs -0 rm -rf deepclean: clean @@ -32,7 +33,7 @@ dev-%: ${PIPRUN} pip install -e .[$*] -c constraints/$(or $(SS_CONSTRAINTS_VERSION),default).txt dev: - ${PIPRUN} pip install -e .[docs,lint,package,test] -c constraints/$(or $(SS_CONSTRAINTS_VERSION),default).txt + ${PIPRUN} pip install -e .[docs,lint,package,tests] -c constraints/$(or $(SS_CONSTRAINTS_VERSION),default).txt -[ "${CI}" != "true" ] && pre-commit install --hook-type pre-push version: @@ -58,8 +59,8 @@ ruff: toml-sort: ${PIPRUN} toml-sort -a -i pyproject.toml -test: - ${PIPRUN} python -m pytest --cov=src --cov-fail-under=$(or $(SS_TEST_COVERAGE_THRESHOLD),0) . +tests: + ${PIPRUN} python -m pytest . freeze: @${PIPRUN} pip freeze --exclude-editable diff --git a/Makefile.jinja b/Makefile.jinja new file mode 100644 index 00000000..6295a002 --- /dev/null +++ b/Makefile.jinja @@ -0,0 +1,80 @@ +.PHONY: clean deepclean install dev version pre-commit lint black mypy ruff toml-sort tests freeze build upload docs docs-autobuild + +# Construct pipenv run command with or without site-packages flag when not in CI environment and pipenv command exists. +SITE_PACKAGES_FLAG = $(shell [ "${SS_SITE_PACKAGES}" = "true" ] && echo --site-packages) +PIPRUN := $(shell [ "${CI}" != "true" ] && command -v pipenv > /dev/null 2>&1 && echo pipenv ${SITE_PACKAGES_FLAG} run) + +# Remove common intermediate files. +clean: + -rm -rf \ +{%- if project_name == "Serious Scaffold Python" %} + .copier-answers.yml \ +{%- endif %} + .coverage \ + .mypy_cache \ + .pytest_cache \ + .ruff_cache \ + Pipfile* \ + coverage.xml \ + dist \ + docs/_build + find . -name '*.egg-info' -print0 | xargs -0 rm -rf + find . -name '*.pyc' -print0 | xargs -0 rm -f + find . -name '*.swp' -print0 | xargs -0 rm -f + find . -name '.DS_Store' -print0 | xargs -0 rm -r + find . -name '__pycache__' -print0 | xargs -0 rm -rf + +deepclean: clean + -pre-commit uninstall --hook-type pre-push + -pipenv --venv >/dev/null 2>&1 && pipenv --rm + +install: + ${PIPRUN} pip install -e . -c constraints/$(or $(SS_CONSTRAINTS_VERSION),default).txt + +dev-%: + ${PIPRUN} pip install -e .[$*] -c constraints/$(or $(SS_CONSTRAINTS_VERSION),default).txt + +dev: + ${PIPRUN} pip install -e .[docs,lint,package,tests] -c constraints/$(or $(SS_CONSTRAINTS_VERSION),default).txt + -[ "${CI}" != "true" ] && pre-commit install --hook-type pre-push + +version: + ${PIPRUN} python -m setuptools_scm + +pre-commit: + pre-commit run --all-files + +black: + ${PIPRUN} python -m black docs tests src + +lint: isort mypy ruff toml-sort + +isort: + ${PIPRUN} python -m isort . + +mypy: + ${PIPRUN} python -m mypy docs tests src + +ruff: + ${PIPRUN} python -m ruff docs tests src + +toml-sort: + ${PIPRUN} toml-sort -a -i pyproject.toml + +tests: + ${PIPRUN} python -m pytest . + +freeze: + @${PIPRUN} pip freeze --exclude-editable + +build: + ${PIPRUN} python -m build + +upload: + ${PIPRUN} python -m twine upload dist/* + +docs: + ${PIPRUN} python -m sphinx.cmd.build docs docs/_build + +docs-autobuild: + ${PIPRUN} python -m sphinx_autobuild docs docs/_build diff --git a/README.md b/README.md index 94fe51a7..4e3ecdbe 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ A serious Python project template for out-of-box and production usage. [![GitHub](https://img.shields.io/github/license/huxuan/serious-scaffold-python)](https://github.com/huxuan/serious-scaffold-python/blob/main/LICENSE) -[![Lint & Test Status](https://github.com/huxuan/serious-scaffold-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/huxuan/serious-scaffold-python/actions/workflows/ci.yml) +[![CI Status](https://github.com/huxuan/serious-scaffold-python/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/huxuan/serious-scaffold-python/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/huxuan/serious-scaffold-python/branch/main/graph/badge.svg?token=4JPKXI122N)](https://codecov.io/gh/huxuan/serious-scaffold-python) -[![Documentation Status](https://readthedocs.org/projects/serious-scaffold-python/badge/?version=latest)](https://serious-scaffold-python.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/serious-scaffold-python/badge/)](https://serious-scaffold-python.readthedocs.io/) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) @@ -34,12 +34,28 @@ If you find this helpful, please consider [sponsorship](https://github.com/spons - [`pre-commit`](https://github.com/pre-commit/pre-commit) with [general hooks](https://github.com/pre-commit/pre-commit-hooks) and local linters. - `Makefile` as the entry point for common actions. - VSCode settings with recommended extensions. -- GitHub workflows for lint, test, package and documentation preview. +- GitHub workflows for lint, tests, package and documentation preview. + +## Usage + +1. [Install Copier](https://copier.readthedocs.io/en/stable/#installation). +1. Generate the project with the following command. + + ``` + copier gh:huxuan/serious-scaffold-python /path/to/project + ``` + +1. Initialize the project with git. + + ``` + cd /path/to/project && git init + ``` + +1. Happy hacking. ## Roadmap - More detailed documentation for all aspects. -- [Copier](https://copier.readthedocs.io/) integration. - [GitHub Dependabot](https://github.com/dependabot) integration. - [GitHub issue and pull request templates](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests). - [Gitlab CI/CD](https://docs.gitlab.com/ee/ci/) integration. diff --git a/README.md.jinja b/README.md.jinja new file mode 100644 index 00000000..7a00c5db --- /dev/null +++ b/README.md.jinja @@ -0,0 +1,54 @@ +# {{ project_name }} + +{{ project_description }} + +[![GitHub](https://img.shields.io/github/license/{{ repo_namespace }}/{{ repo_name }})](https://github.com/{{ repo_namespace }}/{{ repo_name }}/blob/main/LICENSE) +[![CI Status](https://github.com/{{ repo_namespace }}/{{ repo_name }}/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/{{ repo_namespace }}/{{ repo_name }}/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/{{ repo_namespace }}/{{ repo_name }}/branch/main/graph/badge.svg?token=4JPKXI122N)](https://codecov.io/gh/{{ repo_namespace }}/{{ repo_name }}) +[![Documentation Status](https://readthedocs.org/projects/{{ repo_name }}/badge/)](https://{{ repo_name }}.readthedocs.io/) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) +[![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/) +[![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) +[![Serious Scaffold Python](https://img.shields.io/badge/serious%20scaffold-python-blue)](https://github.com/huxuan/serious-scaffold-python) +[![PyPI](https://img.shields.io/pypi/v/{{ package_name }})](https://pypi.org/project/{{ package_name }}/) +[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/{{ package_name }})](https://pypi.org/project/{{ package_name }}/) + +
+ + {{ project_name }} + +
+ +Many efforts have been made to ease the project setup, but most of them are only language-specified basic components. In practice, we have to deal with much more details, especially for team projects. Many commonly used tools and configurations need to be handled properly. Moreover, different people tend to have different favors in various aspects. If you are tired of the inefficient setup process and endless discussion, [Serious Scaffold Python](https://github.com/huxuan/serious-scaffold-python) is here to terminate those for Python Projects. + +If you find this helpful, please consider [sponsorship](https://github.com/sponsors/huxuan). + +## Features + +- Basic Python project structure as a package with tests and documentation. +- Categorized requirements management with constraints for different environments. +- [`typer`](https://github.com/tiangolo/typer) for CLI with tests and automatic documentation generation. +- [`pydantic`](https://github.com/pydantic/pydantic) for [settings](https://pydantic-docs.helpmanual.io/usage/settings/) with tests and documentation as module samples. +- [`setuptools-scm`](https://github.com/pypa/setuptools_scm/) to extract the version for the package. +- [`black`](https://github.com/psf/black), [`isort`](https://pycqa.github.io/isort/), [`mypy`](http://www.mypy-lang.org/), [`ruff`](https://github.com/charliermarsh/ruff) and [`toml-sort`](https://github.com/pappasam/toml-sort) as linters. +- [`pre-commit`](https://github.com/pre-commit/pre-commit) with [general hooks](https://github.com/pre-commit/pre-commit-hooks) and local linters. +- `Makefile` as the entry point for common actions. +- VSCode settings with recommended extensions. +- GitHub workflows for lint, tests, package and documentation preview. + +## Roadmap + +- More detailed documentation for all aspects. +- [Copier](https://copier.readthedocs.io/) integration. +- [GitHub Dependabot](https://github.com/dependabot) integration. +- [GitHub issue and pull request templates](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests). +- [Gitlab CI/CD](https://docs.gitlab.com/ee/ci/) integration. +- [Gitlab issue and merge request templates](https://docs.gitlab.com/ee/user/project/description_templates.html). + +## License + +MIT + +## Contributing + +Any suggestions, discussions and bug fixing are all welcome. diff --git a/copier.yaml b/copier.yaml new file mode 100644 index 00000000..67f792da --- /dev/null +++ b/copier.yaml @@ -0,0 +1,51 @@ +_exclude: + - "*.py[co]" + - ".DS_Store" + - ".git" + - ".svn" + - "__pycache__" + - "copier.yaml" + - "copier.yml" + - "docs/modules/serious_scaffold.settings.rst" + - "src/serious_scaffold" + - "~*" + +project_name: + type: str + help: "Project name in CamelCase:" + default: Serious Scaffold Python + +project_description: + type: str + help: "Brief project description:" + default: A serious Python project template for out-of-box and production usage. + +repo_namespace: + type: str + help: "Repo namespace, it should be the name of an user or an organization:" + default: huxuan + +author_name: + type: str + help: "Author name:" + default: "{{ repo_namespace }}" + +author_email: + type: str + help: "Author email:" + default: "i@{{ author_name }}.org" + +repo_name: + type: str + help: "Repo name:" + default: "{{ project_name|lower|replace(' ', '-') }}" + +package_name: + type: str + help: "Package name:" + default: "{{ repo_name|regex_replace('-python$','') }}" + +module_name: + type: str + help: "Module name:" + default: "{{ package_name|lower|replace('-', '_') }}" diff --git a/docs/cli/index.rst.jinja b/docs/cli/index.rst.jinja new file mode 100644 index 00000000..da395198 --- /dev/null +++ b/docs/cli/index.rst.jinja @@ -0,0 +1,6 @@ +Command Line Interface +====================== + +.. click:: {{ module_name }}.cli:typer_click_object + :prog: {{ package_name }}-cli + :nested: full diff --git a/docs/conf.py b/docs/conf.py index b2aa3af0..274f2d4a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -9,11 +9,10 @@ # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -project = "serious-scaffold" -package = project.replace("-", "_") -author = metadata.metadata(package)["Author"] -copyright = f"2022, {author}" # noqa: A001 -release = metadata.version(package) +author = "huxuan" +copyright = "2022, huxuan" # noqa: A001 +project = "Serious Scaffold Python" +release = metadata.version("serious-scaffold") version = ".".join(release.split(".")[:2]) diff --git a/docs/conf.py.jinja b/docs/conf.py.jinja new file mode 100644 index 00000000..678aff2c --- /dev/null +++ b/docs/conf.py.jinja @@ -0,0 +1,40 @@ +"""Configuration file for the Sphinx documentation builder. + +For the full list of built-in configuration values, see the documentation: +https://www.sphinx-doc.org/en/master/usage/configuration.html +""" + +from importlib import metadata + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +author = "{{ author_name }}" +copyright = "2022, {{ author_name }}" # noqa: A001 +project = "{{ project_name }}" +release = metadata.version("{{ package_name }}") +version = ".".join(release.split(".")[:2]) + + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_click", + "sphinxcontrib.autodoc_pydantic", +] + +templates_path = ["_templates"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +autodoc_pydantic_settings_show_field_summary = False +autodoc_pydantic_settings_show_json = False + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = "furo" +html_static_path = ["_static"] diff --git a/docs/index.rst b/docs/index.rst index 2fbb07a2..20e61e8e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,5 +1,5 @@ -Welcome to serious-scaffold's documentation! -============================================ +Welcome to Serious Scaffold Python's documentation! +=================================================== .. toctree:: :maxdepth: 2 diff --git a/docs/index.rst.jinja b/docs/index.rst.jinja new file mode 100644 index 00000000..64582c56 --- /dev/null +++ b/docs/index.rst.jinja @@ -0,0 +1,15 @@ +Welcome to {{ project_name }}'s documentation! +=================================================== + +.. toctree:: + :maxdepth: 2 + + cli/index + modules/index + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/modules/index.rst.jinja b/docs/modules/index.rst.jinja new file mode 100644 index 00000000..a5a3e818 --- /dev/null +++ b/docs/modules/index.rst.jinja @@ -0,0 +1,7 @@ +Modules +======= + +.. toctree:: + :maxdepth: 1 + + {{ module_name }}.settings diff --git a/docs/modules/{{ module_name }}.settings.rst.jinja b/docs/modules/{{ module_name }}.settings.rst.jinja new file mode 100644 index 00000000..cb92c2f9 --- /dev/null +++ b/docs/modules/{{ module_name }}.settings.rst.jinja @@ -0,0 +1,5 @@ +{{ module_name }}.settings +========================= + +.. automodule:: {{ module_name }}.settings + :members: diff --git a/pyproject.toml b/pyproject.toml index 94de545a..706dbaf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,6 @@ requires = [ [project] authors = [ - {name = "Serious Scaffold"}, {name = "huxuan", email = "i@huxuan.org"}, ] classifiers = [ @@ -19,7 +18,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] -description = "A serious python project template for out-of-box and production usage." +description = "A serious Python project template for out-of-box and production usage." dynamic = [ "version", "dependencies", @@ -31,7 +30,7 @@ keywords = [ "project template", "template", ] -name = "serious_scaffold" +name = "serious-scaffold" readme = "README.md" requires-python = ">=3.8" @@ -42,6 +41,14 @@ serious-scaffold-cli = "serious_scaffold.cli:app" homepage = "https://github.com/huxuan/serious-scaffold-python/" issue = "https://github.com/huxuan/serious-scaffold-python/issues" +[tool.coverage.report] +fail_under = 100 + +[tool.coverage.run] +omit = [ + "src/{{ module_name }}/*", +] + [tool.mypy] check_untyped_defs = true disallow_any_unimported = true @@ -49,13 +56,16 @@ disallow_untyped_defs = true enable_error_code = [ "ignore-without-code", ] +exclude = [ + "src/{{ module_name }}", +] no_implicit_optional = true show_error_codes = true warn_return_any = true warn_unused_ignores = true [tool.pytest.ini_options] -addopts = "-l -s -v --color=yes --durations=0 --cov-report xml" +addopts = "-l -s --durations=0 --cov=src --cov-report term --cov-report xml" log_cli = true log_cli_level = "info" log_date_format = "%Y-%m-%d %H:%M:%S" @@ -108,6 +118,6 @@ dependencies = {file = ["requirements.txt"]} docs = {file = ["requirements/docs.txt"]} lint = {file = ["requirements/lint.txt"]} package = {file = ["requirements/package.txt"]} -test = {file = ["requirements/test.txt"]} +tests = {file = ["requirements/tests.txt"]} [tool.setuptools_scm] diff --git a/pyproject.toml.jinja b/pyproject.toml.jinja new file mode 100644 index 00000000..3a869cf0 --- /dev/null +++ b/pyproject.toml.jinja @@ -0,0 +1,127 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "setuptools", + "setuptools-scm", +] + +[project] +authors = [ + {name = "{{ author_name }}", email = "{{ author_email }}"}, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +description = "{{ project_description }}" +dynamic = [ + "version", + "dependencies", + "optional-dependencies", +] +keywords = [ + "out-of-box", + "production", + "project template", + "template", +] +name = "{{ package_name }}" +readme = "README.md" +requires-python = ">=3.8" + +[project.scripts] +{{ package_name }}-cli = "{{ module_name }}.cli:app" + +[project.urls] +homepage = "https://github.com/{{ repo_namespace }}/{{ repo_name }}/" +issue = "https://github.com/{{ repo_namespace }}/{{ repo_name }}/issues" + +[tool.coverage.report] +fail_under = 100 + +[tool.coverage.run] +omit = [ +{%- if project_name == "Serious Scaffold Python" %} + "src/{{ '{{ module_name }}' }}/*", +{%- endif %} +] + +[tool.mypy] +check_untyped_defs = true +disallow_any_unimported = true +disallow_untyped_defs = true +enable_error_code = [ + "ignore-without-code", +] +exclude = [ +{%- if project_name == "Serious Scaffold Python" %} + "src/{{ '{{ module_name }}' }}", +{%- endif %} +] +no_implicit_optional = true +show_error_codes = true +warn_return_any = true +warn_unused_ignores = true + +[tool.pytest.ini_options] +addopts = "-l -s --durations=0 --cov=src --cov-report term --cov-report xml" +log_cli = true +log_cli_level = "info" +log_date_format = "%Y-%m-%d %H:%M:%S" +log_format = "%(asctime)s %(levelname)s %(message)s" +minversion = "6.0" + +[tool.ruff] +extend-ignore = [ + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +extend-select = [ + "I", + "D", + "U", + "N", + "S", + "C", + "FBT", + "B", + "A", + "I25", + "T", + "Q", + "ANN", + "YTT", + "BLE", + "C90", + "RUF", + "M", +] +fix = true +per-file-ignores = {"tests/*" = ["S101"]} +src = ["src"] +target-version = "py38" + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + +[tool.setuptools.dynamic.optional-dependencies] +docs = {file = ["requirements/docs.txt"]} +lint = {file = ["requirements/lint.txt"]} +package = {file = ["requirements/package.txt"]} +tests = {file = ["requirements/tests.txt"]} + +[tool.setuptools_scm] diff --git a/requirements/test.txt b/requirements/tests.txt similarity index 100% rename from requirements/test.txt rename to requirements/tests.txt diff --git a/src/{{ module_name }}/__init__.py b/src/{{ module_name }}/__init__.py new file mode 100644 index 00000000..8a8721b5 --- /dev/null +++ b/src/{{ module_name }}/__init__.py @@ -0,0 +1 @@ +"""Init for the project.""" diff --git a/src/{{ module_name }}/cli.py b/src/{{ module_name }}/cli.py new file mode 100644 index 00000000..5a4957d9 --- /dev/null +++ b/src/{{ module_name }}/cli.py @@ -0,0 +1,16 @@ +"""Command Line Interface.""" +import typer + +app = typer.Typer() + + +@app.command() +def run() -> None: + """Run command.""" + + +typer_click_object = typer.main.get_command(app) + + +if __name__ == "__main__": + app() # pragma: no cover diff --git a/src/{{ module_name }}/settings.py.jinja b/src/{{ module_name }}/settings.py.jinja new file mode 100644 index 00000000..f46d00ee --- /dev/null +++ b/src/{{ module_name }}/settings.py.jinja @@ -0,0 +1,30 @@ +"""Settings Module.""" +import logging +from logging import getLevelName +from typing import Optional + +from pydantic import BaseSettings + + +class Settings(BaseSettings): + """Project specific settings.""" + + logging_level: Optional[str] = getLevelName(logging.INFO) + + class Config: + """Config for settings.""" + + env_prefix = "{{ module_name|upper }}_" + + +class GlobalSettings(BaseSettings): + """System level settings.""" + + ci: bool = False + + +#: Instance for project specific settings. +settings = Settings() + +#: Instance for system level settings. +global_settings = GlobalSettings() diff --git a/tests/cli_test.py b/tests/cli_test.py index f20268cf..c3bf2eb4 100644 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -7,7 +7,7 @@ def test_app() -> None: - """Test for cli.""" + """Tests for cli.""" result = runner.invoke(app) assert result.exit_code == 0 assert result.output == "" diff --git a/tests/cli_test.py.jinja b/tests/cli_test.py.jinja new file mode 100644 index 00000000..73565fbb --- /dev/null +++ b/tests/cli_test.py.jinja @@ -0,0 +1,13 @@ +"""Tests for cli.""" +from typer.testing import CliRunner + +from {{ module_name }}.cli import app + +runner = CliRunner() + + +def test_app() -> None: + """Tests for cli.""" + result = runner.invoke(app) + assert result.exit_code == 0 + assert result.output == "" diff --git a/tests/pkg_test.py b/tests/pkg_test.py index 1fc66d8b..35e7fbe5 100644 --- a/tests/pkg_test.py +++ b/tests/pkg_test.py @@ -1,8 +1,8 @@ -"""Unit tests for pkg.""" +"""Tests for pkg.""" import serious_scaffold def test_pkg() -> None: - """Unit test for pkg.""" + """Tests for pkg.""" assert serious_scaffold.__package__ == "serious_scaffold" diff --git a/tests/pkg_test.py.jinja b/tests/pkg_test.py.jinja new file mode 100644 index 00000000..20a8a7e9 --- /dev/null +++ b/tests/pkg_test.py.jinja @@ -0,0 +1,8 @@ +"""Tests for pkg.""" + +import {{ module_name }} + + +def test_pkg() -> None: + """Tests for pkg.""" + assert {{ module_name }}.__package__ == "{{ module_name }}" diff --git a/tests/settings_test.py b/tests/settings_test.py index 1bfda499..3c7dee5c 100644 --- a/tests/settings_test.py +++ b/tests/settings_test.py @@ -5,6 +5,6 @@ def test_settings() -> None: - """Test for default settings.""" + """Tests for settings.""" assert settings.logging_level == os.getenv("SERIOUS_SCAFFOLD_LOGGING_LEVEL", "INFO") assert str(global_settings.ci).lower() == os.getenv("CI", "False").lower() diff --git a/tests/settings_test.py.jinja b/tests/settings_test.py.jinja new file mode 100644 index 00000000..464eb721 --- /dev/null +++ b/tests/settings_test.py.jinja @@ -0,0 +1,10 @@ +"""Tests for settings.""" +import os + +from {{ module_name }}.settings import global_settings, settings + + +def test_settings() -> None: + """Tests for settings.""" + assert settings.logging_level == os.getenv("{{ module_name|upper }}_LOGGING_LEVEL", "INFO") + assert str(global_settings.ci).lower() == os.getenv("CI", "False").lower() diff --git a/{{_copier_conf.answers_file}}.jinja b/{{_copier_conf.answers_file}}.jinja new file mode 100644 index 00000000..2e5b1f4e --- /dev/null +++ b/{{_copier_conf.answers_file}}.jinja @@ -0,0 +1 @@ +{{_copier_answers|to_nice_yaml}}