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 }}/)
+
+
+
+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}}