Skip to content
This repository has been archived by the owner on Feb 7, 2024. It is now read-only.
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: TezRomacH/python-package-template
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.5
Choose a base ref
...
head repository: TezRomacH/python-package-template
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Loading
Showing with 759 additions and 378 deletions.
  1. +24 −5 Makefile
  2. +27 −8 README.md
  3. +21 −0 assets/images/coverage.svg
  4. +42 −23 hooks/post_gen_project.py
  5. +29 −21 hooks/pre_gen_project.py
  6. +425 −226 poetry.lock
  7. +46 −10 pyproject.toml
  8. +0 −22 requirements.txt
  9. +1 −13 setup.cfg
  10. +0 −6 tests/test_example/test_pass.py
  11. +13 −0 tests/test_utils.py
  12. +0 −16 {{ cookiecutter.project_name }}/setup.cfg
  13. 0 ...ject_name }}/{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}/py.typed
  14. 0 ...e }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.dockerignore
  15. 0 ...e }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.editorconfig
  16. 0 ...→ {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/.stale.yml
  17. 0 ...project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/ISSUE_TEMPLATE/bug_report.md
  18. 0 ...er.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/ISSUE_TEMPLATE/config.yml
  19. 0 ...ct_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/ISSUE_TEMPLATE/feature_request.md
  20. 0 ...r.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/ISSUE_TEMPLATE/question.md
  21. 0 ...ter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/PULL_REQUEST_TEMPLATE.md
  22. +1 −1 ... cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/dependabot.yml
  23. 0 ...iecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/release-drafter.yml
  24. 0 ...iecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/workflows/build.yml
  25. 0 ...tter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/workflows/greetings.yml
  26. 0 ...roject_name.lower().replace(' ', '_').replace('-', '_') }}}/.github/workflows/release-drafter.yml
  27. 0 ...name }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.gitignore
  28. 0 ...cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/.pre-commit-config.yaml
  29. 0 ...→ {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/CODE_OF_CONDUCT.md
  30. 0 ...}} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/CONTRIBUTING.md
  31. +32 −8 ...t_name }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/Makefile
  32. +25 −6 ..._name }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/README.md
  33. 0 ...ame }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/SECURITY.md
  34. 0 ...{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/_licences/apache.txt
  35. 0 ...→ {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/_licences/bsd3.txt
  36. 0 ...→ {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/_licences/gpl3.txt
  37. 0 ... → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/_licences/mit.txt
  38. +21 −0 ...okiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}/assets/images/coverage.svg
  39. 0 ...ecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/cookiecutter-config-file.yml
  40. 0 ... → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/docker/Dockerfile
  41. +1 −1 ...} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/docker/README.md
  42. +47 −12 ... }} → {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/pyproject.toml
  43. +4 −0 {{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}/setup.cfg
  44. 0 ...ter.project_name.lower().replace(' ', '_').replace('-', '_') }}}/tests/test_example/test_hello.py
  45. 0 ... '_') }}}/{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}/__init__.py
  46. 0 ... '_') }}}/{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}/__main__.py
  47. 0 ..., '_') }}}/{{ cookiecutter.project_name.lower().replace(' ', '_').replace('-', '_') }}/example.py
29 changes: 24 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#* Variables
SHELL := /usr/bin/env bash
PYTHON := python
PYTHONPATH := `pwd`

#* Poetry
.PHONY: poetry-download
@@ -16,6 +17,7 @@ poetry-remove:
install:
poetry lock -n && poetry export --without-hashes > requirements.txt
poetry install -n
-poetry run mypy --install-types --non-interactive hooks tests

.PHONY: pre-commit-install
pre-commit-install:
@@ -34,7 +36,8 @@ formatting: codestyle
#* Linting
.PHONY: test
test:
poetry run pytest
PYTHONPATH=$(PYTHONPATH) poetry run pytest -c pyproject.toml --cov-report=html --cov=hooks tests/
poetry run coverage-badge -o assets/images/coverage.svg -f

.PHONY: check-codestyle
check-codestyle:
@@ -44,7 +47,7 @@ check-codestyle:

.PHONY: mypy
mypy:
poetry run mypy --install-types --non-interactive --show-traceback --config-file pyproject.toml ./
poetry run mypy --config-file pyproject.toml hooks tests

.PHONY: check-safety
check-safety:
@@ -57,17 +60,33 @@ lint: test check-codestyle mypy check-safety

.PHONY: update-dev-deps
update-dev-deps:
poetry add -D bandit@latest darglint@latest "isort[colors]@latest" mypy@latest pre-commit@latest pydocstyle@latest pylint@latest pytest@latest pyupgrade@latest safety@latest
poetry add -D bandit@latest darglint@latest "isort[colors]@latest" mypy@latest pre-commit@latest pydocstyle@latest pylint@latest pytest@latest pyupgrade@latest safety@latest coverage@latest coverage-badge@latest pytest-html@latest pytest-cov@latest
poetry add -D --allow-prereleases black@latest

#* Cleaning
.PHONY: pycache-remove
pycache-remove:
find . | grep -E "(__pycache__|\.pyc|\.pyo$$)" | xargs rm -rf

.PHONY: dsstore-remove
dsstore-remove:
find . | grep -E ".DS_Store" | xargs rm -rf

.PHONY: mypycache-remove
mypycache-remove:
find . | grep -E ".mypy_cache" | xargs rm -rf

.PHONY: ipynbcheckpoints-remove
ipynbcheckpoints-remove:
find . | grep -E ".ipynb_checkpoints" | xargs rm -rf

.PHONY: pytestcache-remove
pytestcache-remove:
find . | grep -E ".pytest_cache" | xargs rm -rf

.PHONY: build-remove
build-remove:
rm -rf build/

.PHONY: clean-all
clean-all: pycache-remove build-remove
.PHONY: cleanup
cleanup: pycache-remove dsstore-remove mypycache-remove ipynbcheckpoints-remove pytestcache-remove
35 changes: 27 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -10,14 +10,15 @@
[![Pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/TezRomacH/python-package-template/blob/master/.pre-commit-config.yaml)
[![Semantic Versions](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--versions-e10079.svg)](https://github.com/TezRomacH/python-package-template/releases)
[![License](https://img.shields.io/github/license/TezRomacH/python-package-template)](https://github.com/TezRomacH/python-package-template/blob/master/LICENSE)
![Coverage Report](assets/images/coverage.svg)

Your next Python package needs a bleeding-edge project structure.
</div>

## TL;DR

```bash
cookiecutter gh:TezRomacH/python-package-template --checkout v1.0.4
cookiecutter gh:TezRomacH/python-package-template --checkout v1.1.1
```

> All you need is the latest version of cookiecutter 😉
@@ -65,7 +66,7 @@ pip install -U cookiecutter
then go to a directory where you want to create your project and run:

```bash
cookiecutter gh:TezRomacH/python-package-template --checkout v1.0.4
cookiecutter gh:TezRomacH/python-package-template --checkout v1.1.1
```

### Input variables
@@ -213,6 +214,15 @@ make check-codestyle

> Note: `check-codestyle` uses `isort`, `black` and `darglint` library
Update all dev libraries to the latest version using one comand

```bash
make update-dev-deps
```

</p>
</details>

<details>
<summary>4. Code security</summary>
<p>
@@ -230,9 +240,6 @@ make check-safety
</p>
</details>

</p>
</details>

<details>
<summary>5. Type checks</summary>
<p>
@@ -247,7 +254,7 @@ make mypy
</details>

<details>
<summary>6. Tests</summary>
<summary>6. Tests with coverage badges</summary>
<p>

Run `pytest`
@@ -318,10 +325,22 @@ Remove package build
make build-remove
```

Or to remove pycache, build and docker image run:
Delete .DS_STORE files

```bash
make dsstore-remove
```

Remove .mypycache

```bash
make mypycache-remove
```

Or to remove all above run:

```bash
make clean-all
make cleanup
```

</p>
21 changes: 21 additions & 0 deletions assets/images/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 42 additions & 23 deletions hooks/post_gen_project.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"""This module is called after project is created."""

from typing import Callable, List
from typing import List

import textwrap
from pathlib import Path
@@ -19,42 +18,58 @@
# Values to generate github repository
GITHUB_USER = "{{ cookiecutter.github_name }}"

licenses = {
licences_dict = {
"MIT": "mit",
"BSD-3": "bsd3",
"GNU GPL v3.0": "gpl3",
"Apache Software License 2.0": "apache",
}


def generate_license() -> None:
"""Generate license file for the project."""
move(f"{PROJECT_DIRECTORY}/_licences/{licenses[LICENSE]}.txt", f"{PROJECT_DIRECTORY}/LICENSE")
rmtree(f"{PROJECT_DIRECTORY}/_licences/")
def generate_license(directory: Path, licence: str) -> None:
"""Generate license file for the project.
Args:
directory: path to the project directory
licence: chosen licence
"""
move(str(directory / "_licences" / f"{licence}.txt"), str(directory / "LICENSE"))
rmtree(str(directory / "_licences"))


def remove_unused_files(directory: Path, module_name: str, need_to_remove_cli: bool) -> None:
"""Remove unused files.
def remove_unused_files() -> None:
"""Remove unused files."""
Args:
directory: path to the project directory
module_name: project module name
need_to_remove_cli: flag for removing CLI related files
"""
files_to_delete: List[Path] = []

def _cli_specific_files() -> List[Path]:
return [Path(f"{PROJECT_DIRECTORY}/{PROJECT_MODULE}/__main__.py")]
return [directory / module_name / "__main__.py"]

if CREATE_EXAMPLE_TEMPLATE != "cli":
if need_to_remove_cli:
files_to_delete.extend(_cli_specific_files())

for path in files_to_delete:
path.unlink()


def print_futher_instuctions() -> None:
"""Show user what to do next after project creation."""
def print_futher_instuctions(project_name: str, github: str) -> None:
"""Show user what to do next after project creation.
Args:
project_name: current project name
github: GitHub username
"""
message = f"""
Your project {PROJECT_NAME} is created.
Your project {project_name} is created.
1) Now you can start working on it:
$ cd {PROJECT_NAME} && git init
$ cd {project_name} && git init
2) If you don't have Poetry installed run:
@@ -74,17 +89,21 @@ def print_futher_instuctions() -> None:
$ git add .
$ git commit -m ":tada: Initial commit"
$ git branch -M main
$ git remote add origin https://github.com/{GITHUB_USER}/{PROJECT_NAME}.git
$ git remote add origin https://github.com/{github}/{project_name}.git
$ git push -u origin main
"""
print(textwrap.dedent(message))


post_functions: List[Callable[[], None]] = [
generate_license,
remove_unused_files,
print_futher_instuctions,
]
def main() -> None:
generate_license(directory=PROJECT_DIRECTORY, licence=licences_dict[LICENSE])
remove_unused_files(
directory=PROJECT_DIRECTORY,
module_name=PROJECT_MODULE,
need_to_remove_cli=CREATE_EXAMPLE_TEMPLATE != "cli",
)
print_futher_instuctions(project_name=PROJECT_NAME, github=GITHUB_USER)


for fn in post_functions:
fn()
if __name__ == "__main__":
main()
50 changes: 29 additions & 21 deletions hooks/pre_gen_project.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""This module is called before project is created."""

from typing import Callable, List

import re
import sys

PROJECT_NAME = "{{ cookiecutter.project_name }}"
PROJECT_VERSION = "{{ cookiecutter.version }}"
LINE_LENGTH_PARAMETER = "{{ cookiecutter.line_length }}"


MODULE_REGEX = re.compile(r"^[a-z][a-z0-9\-\_]+[a-z0-9]$")
SEMVER_REGEX = re.compile(
r"""
@@ -27,28 +30,30 @@
re.VERBOSE,
)

module_name = "{{ cookiecutter.project_name }}"
version = "{{ cookiecutter.version }}"
line_length = "{{ cookiecutter.line_length }}"


def validate_project_name() -> None:
def validate_project_name(project_name: str) -> None:
"""Ensure that `project_name` parameter is valid.
Valid inputs starts with the lowercase letter.
Followed by any lowercase letters, numbers or underscores.
Args:
project_name: current project name
Raises:
ValueError: If module_name is not a valid Python module name
ValueError: If project_name is not a valid Python module name
"""
if MODULE_REGEX.fullmatch(module_name) is None:
message = f"ERROR: The project name `{module_name}` is not a valid Python module name."
if MODULE_REGEX.fullmatch(project_name) is None:
message = f"ERROR: The project name `{project_name}` is not a valid Python module name."
raise ValueError(message)


def validate_semver() -> None:
def validate_semver(version: str) -> None:
"""Ensure version in semver notation.
Args:
version: string version. For example 0.1.2 or 1.2.4
Raises:
ValueError: If version is not in semver notation
"""
@@ -57,26 +62,29 @@ def validate_semver() -> None:
raise ValueError(message)


def validate_line_length() -> None:
def validate_line_length(line_length: int) -> None:
"""Validate line_length parameter. Length should be between 50 and 300.
Args:
line_length: integer paramenter for isort and black formatters
Raises:
ValueError: If line_length isn't between 50 and 300
"""
if not (50 <= int(line_length) <= 300):
if not (50 <= line_length <= 300):
message = f"ERROR: line_length must be between 50 and 300. Got `{line_length}`."
raise ValueError(message)


validators: List[Callable[[], None]] = [
validate_project_name,
validate_semver,
validate_line_length,
]

for validator in validators:
def main() -> None:
try:
validator()
validate_project_name(project_name=PROJECT_NAME)
validate_semver(version=PROJECT_VERSION)
validate_line_length(line_length=int(LINE_LENGTH_PARAMETER))
except ValueError as ex:
print(ex)
sys.exit(1)


if __name__ == "__main__":
main()
Loading