🌹 Cookiecutter template featuring the modern and extensible Python project manager hatch
import subprocess

try:
    subprocess.call(['git', 'init'])
    subprocess.call(['git', 'add', '*'])
    subprocess.call(['git', 'commit', '-m', 'Initial commit'])
except Exception as e:
    print(f"An error occurred during initializing the git repo: {e}")
    print("Makre sure to manually set up a git repository which is necessary for `hatch-vcs`")

hooks/post_gen_project.py
FlorianWilhelm/the-hatchlor
"""Generate the code reference pages and navigation.

Copyright (c) 2019, Timothée Mazzucotelli, ISC License

Taken from https://github.com/mkdocstrings/mkdocstrings/blob/master/docs/gen_ref_nav.py
"""

from pathlib import Path

import mkdocs_gen_files

nav = mkdocs_gen_files.Nav()

for path in sorted(Path('src').rglob('*.py')):
    module_path = path.relative_to('src').with_suffix('')
    doc_path = path.relative_to('src').with_suffix('.md')
    full_doc_path = Path('reference', doc_path)

    parts = tuple(module_path.parts)

    if parts[-1] == '__init__':
        parts = parts[:-1]
        doc_path = doc_path.with_name('index.md')
        full_doc_path = full_doc_path.with_name('index.md')
    elif parts[-1] == '__main__':
        continue
    elif parts[-1] == '_version':
        continue

    nav[parts] = doc_path.as_posix()

    with mkdocs_gen_files.open(full_doc_path, 'w') as fd:
        ident = '.'.join(parts)
        fd.write(f'::: {ident}')

    mkdocs_gen_files.set_edit_path(full_doc_path, path)

with mkdocs_gen_files.open('reference/SUMMARY.md', 'w') as nav_file:
    nav_file.writelines(nav.build_literate_nav())

{{cookiecutter.project_slug}}/docs/scripts/gen_ref_nav.py
FlorianWilhelm/the-hatchlor
"""Ultimate Notion provides a pythonic, high-level API for Notion

Notion-API: https://developers.notion.com/reference/intro
"""
from importlib.metadata import PackageNotFoundError, version

try:
    __version__ = version('{{ cookiecutter.project_slug }}')
except PackageNotFoundError:  # pragma: no cover
    __version__ = 'unknown'
finally:
    del version, PackageNotFoundError

{{cookiecutter.project_slug}}/src/{{cookiecutter.pkg_name}}/__init__.py
FlorianWilhelm/the-hatchlor
"""
This is a skeleton file that can serve as a starting point for a Python
console script. This is accomplished via the following lines in `pyproject.toml`:

```toml
[project.scripts]
fibonacci = "{{ cookiecutter.pkg_name }}.skeleton:app"
```

Then run `hatch run fibonacci 10` to execute this in your default environment or
`hatch shell` to enter the default environment, followed by `fibonacci 10`.

Note:
    This file can be renamed depending on your needs or safely removed if not needed.

References:
    - https://setuptools.pypa.io/en/latest/userguide/entry_point.html
    - https://pip.pypa.io/en/stable/reference/pip_install
"""
import enum
import logging
import sys

import typer

from {{ cookiecutter.pkg_name }} import __version__

_logger = logging.getLogger(__name__)


class LogLevel(str, enum.Enum):
    CRITICAL = 'critical'
    ERROR = 'error'
    WARNING = 'warning'
    INFO = 'info'
    DEBUG = 'debug'


def fib(n: int) -> int:
    """Fibonacci example function"""
    if not n > 0:
        msg = f'{n} must be larger than 0!'
        raise RuntimeError(msg)
    a, b = 1, 1
    for _ in range(n - 1):
        a, b = b, a + b
    return a


def setup_logging(log_level: LogLevel):
    """Setup basic logging"""
    log_format = '[%(asctime)s] %(levelname)s:%(name)s:%(message)s'
    numeric_level = getattr(logging, log_level.upper(), None)
    logging.basicConfig(level=numeric_level, stream=sys.stdout, format=log_format, datefmt='%Y-%m-%d %H:%M:%S')


app = typer.Typer(
    name=f'{{ cookiecutter.project_name }} {__version__}',
    help='{{ cookiecutter.project_short_description }}',
)


@app.command()
def main(
    n: int = typer.Argument(..., min=1, help='Positive integer'),
    log_level: LogLevel = typer.Option(LogLevel.INFO, help='Log level'),
):
    """Wrapper allowing :func:`fib` to be called with string arguments in a CLI fashion

    Instead of returning the value from :func:`fib`, it prints the result to the
    `stdout` in a nicely formatted message.
    """
    setup_logging(log_level)
    _logger.debug('Starting crazy calculations...')
    print(f'The {n}-th Fibonacci number is {fib(n)}')  # noqa: T201
    _logger.info('Script ends here')


if __name__ == '__main__':
    app()

{{cookiecutter.project_slug}}/src/{{cookiecutter.pkg_name}}/skeleton.py
FlorianWilhelm/the-hatchlor
"""
    Dummy conftest.py for `{{ cookiecutter.project_name }}`.

    If you don't know what this is for, just leave it empty.
    Read more about conftest.py under:
    - https://docs.pytest.org/en/stable/fixture.html
    - https://docs.pytest.org/en/stable/writing_plugins.html
"""

# import pytest

{{cookiecutter.project_slug}}/tests/conftest.py
FlorianWilhelm/the-hatchlor
import pytest

from {{ cookiecutter.pkg_name }}.skeleton import fib, app


def test_fib():
    """API Tests"""
    assert fib(1) == 1
    assert fib(2) == 1
    assert fib(7) == 13
    with pytest.raises(RuntimeError):
        fib(-10)


def test_app(capsys):
    """CLI Tests"""
    # capsys is a pytest fixture that allows asserts against stdout/stderr
    # https://docs.pytest.org/en/stable/capture.html
    with pytest.raises(SystemExit):  # exit code is 0
        app("7")
    captured = capsys.readouterr()
    assert "The 7-th Fibonacci number is 13" in captured.out

{{cookiecutter.project_slug}}/tests/test_skeleton.py
FlorianWilhelm/the-hatchlor