Repository: CentOS/duffy (language: python)

Issue:
Make `poetry install` work
Running `poetry install` verifies the format of author strings, it trips
over the missing '>'.

Fixes: #49

Additionally, this moves deps needed for testing etc. into the `dev-dependencies` section and sorts deps.
Project Boilerplate
# Story

As a contributor to Duffy,
I want basic “Python project boilerplate” in place,
so I can actually develop something.

# Acceptance Criteria

The repository is set up …:

- [x] … with a top-level directory for the code and another for its tests
- [x] … to use poetry for installing it and its dependencies (see below)
- [x] … to use `pytest` and its `cov` plugin for testing and verifying coverage
- [x] … so `black` assumes a line length of 100 characters
- [x] … so `.gitignore` ignores some commonly generated files and directories
- [x] … so `pytest-black`, `pytest-flake8` and `pytest-isort` are dev deps and configured to be run with `pytest`

# Background

Status: Everything is an empty void.

# Background (serious)

These are the dependencies with which we want to build Duffy:

- `fastapi` as the web microframework
- `sqlalchemy` as the ORM encapsulating DB operations
- `psycopg2` to use PostgreSQL
- `pytest`, `pytest-cov` for testing
- `tox` for running the tests, linting etc. in different environments


Discussion / hints:

Picking up the basic package, test & black configuration.

Fix patch:
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 00000000..7c0fbdec
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,12 @@
+[run]
+branch = True
+source = duffy
+omit =
+
+[report]
+precision = 2
+fail_under = 100
+exclude_lines =
+    pragma: no cover
+    def __repr__
+show_missing = True
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..f649bbbd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+# related to package installation
+/dist/
+
+# byte-compiled / optimized Python files
+__pycache__/
+*.py[co]
+
+# coverage related files
+/.coverage
+/coverage.xml
+/htmlcov/
diff --git a/duffy/__init__.py b/duffy/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/duffy/version.py b/duffy/version.py
new file mode 100644
index 00000000..5ddd4274
--- /dev/null
+++ b/duffy/version.py
@@ -0,0 +1,7 @@
+try:
+    from importlib import metadata
+except ImportError:  # pragma: no cover
+    import importlib_metadata as metadata
+
+
+__version__ = metadata.version("duffy")
diff --git a/poetry.lock b/poetry.lock
index 67c0d8ce..355203b3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -182,6 +182,20 @@ python-versions = ">=3.6"
 docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"]
 testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"]
 
+[[package]]
+name = "flake8"
+version = "3.9.2"
+description = "the modular source code checker: pep8 pyflakes and co"
+category = "dev"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
+
+[package.dependencies]
+importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
+mccabe = ">=0.6.0,<0.7.0"
+pycodestyle = ">=2.7.0,<2.8.0"
+pyflakes = ">=2.3.0,<2.4.0"
+
 [[package]]
 name = "greenlet"
 version = "1.1.2"
@@ -269,6 +283,14 @@ requirements_deprecated_finder = ["pipreqs", "pip-api"]
 colors = ["colorama (>=0.4.3,<0.5.0)"]
 plugins = ["setuptools"]
 
+[[package]]
+name = "mccabe"
+version = "0.6.1"
+description = "McCabe checker, plugin for flake8"
+category = "dev"
+optional = false
+python-versions = "*"
+
 [[package]]
 name = "mypy-extensions"
 version = "0.4.3"
@@ -331,6 +353,14 @@ category = "dev"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
 
+[[package]]
+name = "pycodestyle"
+version = "2.7.0"
+description = "Python style guide checker"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
 [[package]]
 name = "pydantic"
 version = "1.8.2"
@@ -347,6 +377,14 @@ typing-extensions = ">=3.7.4.3"
 dotenv = ["python-dotenv (>=0.10.4)"]
 email = ["email-validator (>=1.0.3)"]
 
+[[package]]
+name = "pyflakes"
+version = "2.3.1"
+description = "passive checker of Python programs"
+category = "dev"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
 [[package]]
 name = "pyparsing"
 version = "2.4.7"
@@ -377,6 +415,19 @@ toml = "*"
 [package.extras]
 testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
 
+[[package]]
+name = "pytest-black"
+version = "0.3.12"
+description = "A pytest plugin to enable format checking with black"
+category = "dev"
+optional = false
+python-versions = ">=2.7"
+
+[package.dependencies]
+black = {version = "*", markers = "python_version >= \"3.6\""}
+pytest = ">=3.5.0"
+toml = "*"
+
 [[package]]
 name = "pytest-cov"
 version = "3.0.0"
@@ -392,6 +443,32 @@ pytest = ">=4.6"
 [package.extras]
 testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"]
 
+[[package]]
+name = "pytest-flake8"
+version = "1.0.7"
+description = "pytest plugin to check FLAKE8 requirements"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+flake8 = ">=3.5"
+pytest = ">=3.5"
+
+[[package]]
+name = "pytest-isort"
+version = "2.0.0"
+description = "py.test plugin to check import ordering using isort"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+isort = ">=4.0"
+
+[package.extras]
+tests = ["mock"]
+
 [[package]]
 name = "regex"
 version = "2021.10.8"
@@ -559,7 +636,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6.2"
-content-hash = "3fdd79e4ed09a4296504fe8bfc7bd63a462a8eb2808d15048b928cea924825e4"
+content-hash = "f38a371e48a3cfa2c424f24a020a178b30e21584476e566479c213a986df9603"
 
 [metadata.files]
 anyio = [
@@ -648,6 +725,10 @@ filelock = [
     {file = "filelock-3.3.1-py3-none-any.whl", hash = "sha256:2b5eb3589e7fdda14599e7eb1a50e09b4cc14f34ed98b8ba56d33bfaafcbef2f"},
     {file = "filelock-3.3.1.tar.gz", hash = "sha256:34a9f35f95c441e7b38209775d6e0337f9a3759f3565f6c5798f19618527c76f"},
 ]
+flake8 = [
+    {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
+    {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
+]
 greenlet = [
     {file = "greenlet-1.1.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:58df5c2a0e293bf665a51f8a100d3e9956febfbf1d9aaf8c0677cf70218910c6"},
     {file = "greenlet-1.1.2-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:aec52725173bd3a7b56fe91bc56eccb26fbdff1386ef123abb63c84c5b43b63a"},
@@ -749,6 +830,10 @@ isort = [
     {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"},
     {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"},
 ]
+mccabe = [
+    {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
+    {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
+]
 mypy-extensions = [
     {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
@@ -773,6 +858,10 @@ py = [
     {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"},
     {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"},
 ]
+pycodestyle = [
+    {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"},
+    {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"},
+]
 pydantic = [
     {file = "pydantic-1.8.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:05ddfd37c1720c392f4e0d43c484217b7521558302e7069ce8d318438d297739"},
     {file = "pydantic-1.8.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a7c6002203fe2c5a1b5cbb141bb85060cbff88c2d78eccbc72d97eb7022c43e4"},
@@ -797,6 +886,10 @@ pydantic = [
     {file = "pydantic-1.8.2-py3-none-any.whl", hash = "sha256:fec866a0b59f372b7e776f2d7308511784dace622e0992a0b59ea3ccee0ae833"},
     {file = "pydantic-1.8.2.tar.gz", hash = "sha256:26464e57ccaafe72b7ad156fdaa4e9b9ef051f69e175dbbb463283000c05ab7b"},
 ]
+pyflakes = [
+    {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
+    {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
+]
 pyparsing = [
     {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
     {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
@@ -805,10 +898,21 @@ pytest = [
     {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"},
     {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"},
 ]
+pytest-black = [
+    {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"},
+]
 pytest-cov = [
     {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"},
     {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"},
 ]
+pytest-flake8 = [
+    {file = "pytest-flake8-1.0.7.tar.gz", hash = "sha256:f0259761a903563f33d6f099914afef339c085085e643bee8343eb323b32dd6b"},
+    {file = "pytest_flake8-1.0.7-py2.py3-none-any.whl", hash = "sha256:c28cf23e7d359753c896745fd4ba859495d02e16c84bac36caa8b1eec58f5bc1"},
+]
+pytest-isort = [
+    {file = "pytest-isort-2.0.0.tar.gz", hash = "sha256:821a8c5c9c4f3a3c52cfa9c541fbe89ac9e28728125125af53724c4c3f129117"},
+    {file = "pytest_isort-2.0.0-py3-none-any.whl", hash = "sha256:ab949c593213dad38ba75db32a0ce361fcddd11d4152be4a2c93b85104cc4376"},
+]
 regex = [
     {file = "regex-2021.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072"},
     {file = "regex-2021.10.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361"},
diff --git a/pyproject.toml b/pyproject.toml
index 5b41fc86..ae2b7d93 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,14 +9,24 @@ license = "MIT"
 python = "^3.6.2"
 fastapi = "^0.70.0"
 SQLAlchemy = "^1.4.25"
+importlib-metadata = {version = "^4.8.1", python = "<3.8"}
 
 [tool.poetry.dev-dependencies]
 black = "^21.9b0"
 isort = "^5.9.3"
 pytest = "^6.2.5"
+pytest-black = "^0.3.12"
 pytest-cov = "^3.0.0"
+pytest-flake8 = "^1.0.7"
+pytest-isort = "^2.0.0"
 tox = "^3.24.4"
 
+[tool.pytest.ini_options]
+addopts = "--black --cov-config .coveragerc --cov=duffy --cov-report term --cov-report xml --cov-report html --flake8 --isort"
+
+[tool.black]
+line-length = 100
+
 [build-system]
 requires = ["poetry-core>=1.0.0"]
 build-backend = "poetry.core.masonry.api"


Test patch:
diff --git a/tests/duffy/test_version.py b/tests/duffy/test_version.py
new file mode 100644
index 00000000..24745d50
--- /dev/null
+++ b/tests/duffy/test_version.py
@@ -0,0 +1,14 @@
+from pathlib import Path
+
+import toml
+
+HERE = Path(__file__).parent
+PYPROJECT_TOML_PATH = HERE.parent.parent / "pyproject.toml"
+
+
+def test___version__():
+    from duffy.version import __version__
+
+    pyproject = toml.load(PYPROJECT_TOML_PATH)
+
+    assert __version__ == pyproject["tool"]["poetry"]["version"]