Repository: dh-tech/undate-python (language: python)

Issue:
Convert setup.py to new recommended format of setup.cfg + pyproject.toml
> Any reason to use `setup.py` vs `setup.cfg` + `pyproject.toml`? Think I've usually followed a structure somewhat like this for packages I think will be distributed: https://setuptools.pypa.io/en/latest/userguide/declarative_config.html.

_Originally posted by @ColeDCrawford in https://github.com/dh-tech/hackathon-2022/pull/27#discussion_r1019276580_
      


Discussion / hints:
I try to to a quick fix for transforming to setup.cfg and address #37 as well.

Fix patch:
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index c5bdf24..d79e325 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -18,9 +18,9 @@ jobs:
         with:
           python-version: 3.9
           cache: 'pip'
-          cache-dependency-path: '**/setup.py'
+          cache-dependency-path: '**/setup.cfg'
       - name: Install package with dependencies
         run: pip install -e ".[dev]"
         if: steps.python-cache.outputs.cache-hit != 'true'
       - name: Run black
-        run: black undate --check --diff
+        run: black src --check --diff
diff --git a/.gitignore b/.gitignore
index b7757c8..ac92542 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,10 +26,11 @@ wheels/
 MANIFEST
 
 # Environments
+.tox
 .env
 .venv
 env/
 venv/
 ENV/
 env.bak/
-venv.bak/
\ No newline at end of file
+venv.bak/
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..3943fd8
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,15 @@
+# File: .readthedocs.yaml
+
+version: 2
+
+build:
+  os: "ubuntu-20.04"
+  tools:
+    python: "3.9"
+
+sphinx:
+  configuration: docs/conf.py
+
+python:
+  install:
+    - requirements: docs/requirements.txt
diff --git a/Authors.rst b/Authors.rst
new file mode 100644
index 0000000..6bf2249
--- /dev/null
+++ b/Authors.rst
@@ -0,0 +1,4 @@
+AUTHORS
+=======
+
+DHtech Community
\ No newline at end of file
diff --git a/README.md b/README.md
index f7d76fa..d8c68ad 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,14 @@ pre-commit install
 
 ### Run unit tests
 ```
-pytest
+Tests can be run with either `tox` or `pytest`.  
+
+To run all the tests in a single test file, use pytest and specify the path to the test: `pytest tests/test_dateformat/test_base.py`
+
+To test cases by name, use pytest: `pytest -k test_str`
+### Create documentation
+```
+tox -e docs
 ```
 
 ## Contributors
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d4bb2cb
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS    ?=
+SPHINXBUILD   ?= sphinx-build
+SOURCEDIR     = .
+BUILDDIR      = _build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_static/logo.png b/docs/_static/logo.png
new file mode 100644
index 0000000..bc52174
Binary files /dev/null and b/docs/_static/logo.png differ
diff --git a/docs/authors.rst b/docs/authors.rst
new file mode 100644
index 0000000..30616f6
--- /dev/null
+++ b/docs/authors.rst
@@ -0,0 +1,1 @@
+.. include:: ../Authors.rst
diff --git a/docs/conf.py b/docs/conf.py
new file mode 100644
index 0000000..5e9ca6b
--- /dev/null
+++ b/docs/conf.py
@@ -0,0 +1,63 @@
+# Configuration file for the Sphinx documentation builder.
+#
+# This file only contains a selection of the most common options. For a full
+# list see the documentation:
+# https://www.sphinx-doc.org/en/master/usage/configuration.html
+
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+# import os
+# import sys
+# sys.path.insert(0, os.path.abspath('.'))
+
+
+# -- Project information -----------------------------------------------------
+
+project = 'Undate'
+copyright = '2022, DHtech'
+author = 'DHtech Community'
+
+# The full version, including alpha/beta/rc tags
+release = '0.0.1.dev'
+
+master_doc = 'index'
+
+
+# -- General configuration ---------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    'sphinx.ext.intersphinx',
+    'm2r2'
+]
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+# This pattern also affects html_static_path and html_extra_path.
+exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
+
+source_suffix = [".rst", ".md"]
+
+# -- Options for HTML output -------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+#
+html_theme = 'sphinx_rtd_theme'
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+html_logo = '_static/logo.png'
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..52acdca
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,21 @@
+Undate documentation
+====================
+
+This project ...
+
+
+.. toctree::
+   :maxdepth: 3
+   :caption: Contents:
+
+   readme
+   undate
+   authors
+   license
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/docs/license.rst b/docs/license.rst
new file mode 100644
index 0000000..4f3620b
--- /dev/null
+++ b/docs/license.rst
@@ -0,0 +1,3 @@
+License
+=======
+.. mdinclude:: ../LICENSE.md
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..8084272
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=.
+set BUILDDIR=_build
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+	echo.
+	echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+	echo.installed, then set the SPHINXBUILD environment variable to point
+	echo.to the full path of the 'sphinx-build' executable. Alternatively you
+	echo.may add the Sphinx directory to PATH.
+	echo.
+	echo.If you don't have Sphinx installed, grab it from
+	echo.https://www.sphinx-doc.org/
+	exit /b 1
+)
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/readme.rst b/docs/readme.rst
new file mode 100644
index 0000000..21a7aa4
--- /dev/null
+++ b/docs/readme.rst
@@ -0,0 +1,3 @@
+Introduction
+============
+.. mdinclude:: ../README.md
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..d48afdb
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1,4 @@
+-e .[dev]
+sphinx
+sphinx_rtd_theme
+m2r2
diff --git a/docs/undate.rst b/docs/undate.rst
new file mode 100644
index 0000000..dcaf476
--- /dev/null
+++ b/docs/undate.rst
@@ -0,0 +1,11 @@
+Undate
+======
+
+.. autoclass:: undate.undate.Undate
+   :members:
+   :undoc-members:
+
+
+.. autoclass:: undate.undate.UndateInterval
+   :members:
+   :undoc-members:
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..374b58c
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,6 @@
+[build-system]
+requires = [
+    "setuptools>=42",
+    "wheel"
+]
+build-backend = "setuptools.build_meta"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..a68074c
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,46 @@
+[metadata]
+name = undate
+version = attr: undate.__version__
+author = 'DHTech'
+author_email = 'dhtech.community@gmail.com'
+description = "library for working with uncertain, fuzzy, or "
+    + "partially unknown dates and date intervals"
+long_description = file: README.md
+license="Apache License, Version 2.0",
+long_description_content_type = text/markdown
+url = https://github.com/dh-tech/hackathon-2022
+project_urls =
+    Project Home = https://dh-tech.github.io
+    Bug Tracker = https://github.com/dh-tech/hackathon-2022/-/issues
+classifiers =
+    Development Status :: 2 - Pre-Alpha
+    Programming Language :: Python :: 3
+    Intended Audience :: Developers
+    License :: OSI Approved :: Apache Software License
+    Operating System :: OS Independent
+    Topic :: Software Development :: Libraries :: Python Modules
+    Topic :: Utilities
+    Typing :: Typed
+
+[options]
+package_dir =
+    = src
+packages = find:
+python_requires = >=3.8
+install_requires =
+  python-dateutil
+[options.extras_require]
+all =
+  %(dev)s
+  %(test)s
+dev =
+  black>=22.10.0
+  pre-commit>=2.20.0
+  tox
+  sphinx
+  twine
+test = 
+  pytest>=7.2
+
+[options.packages.find]
+where = src
diff --git a/setup.py b/setup.py
index 3582a0b..1abbd06 100644
--- a/setup.py
+++ b/setup.py
@@ -1,50 +1,4 @@
-import os
-from setuptools import setup
+import setuptools
 
-from undate import __version__
-
-# load the readme contents and use for package long description
-with open(os.path.join(os.path.dirname(__file__), "README.md")) as readme:
-    README = readme.read()
-
-REQUIREMENTS = ["python-dateutil"]
-TEST_REQUIREMENTS = ["pytest>=7.2"]
-DEV_REQUIREMENTS = [
-    "black>=22.10.0",
-    "pre-commit>=2.20.0",
-]
-
-setup(
-    name="undate",
-    version=__version__,
-    description="library for working with uncertain, fuzzy, or "
-    + "partially unknown dates and date intervals",
-    long_description=README,
-    url="https://github.com/dh-tech/hackathon-2022",
-    author="DHTech",
-    author_email="dhtech.community@gmail.com",
-    license="Apache License, Version 2.0",
-    packages=["undate"],
-    install_requires=REQUIREMENTS,
-    tests_require=TEST_REQUIREMENTS,
-    extras_require={
-        "test": TEST_REQUIREMENTS,
-        "dev": TEST_REQUIREMENTS + DEV_REQUIREMENTS,
-    },
-    classifiers=[
-        # any relevant environment classifiers ?
-        "Development Status :: 2 - Pre-Alpha",
-        "Intended Audience :: Developers",
-        "License :: OSI Approved :: Apache Software License",
-        "Operating System :: OS Independent",
-        "Programming Language :: Python",
-        "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: 3.9",
-        "Programming Language :: Python :: 3.10",
-        "Programming Language :: Python :: 3.11",
-        "Topic :: Software Development :: Libraries :: Python Modules",
-        "Topic :: Utilities",
-        "Typing :: Typed",
-    ],
-)
+if __name__ == "__main__":
+    setuptools.setup()
diff --git a/undate/__init__.py b/src/undate/__init__.py
similarity index 100%
rename from undate/__init__.py
rename to src/undate/__init__.py
diff --git a/undate/dateformat/__init__.py b/src/undate/dateformat/__init__.py
similarity index 100%
rename from undate/dateformat/__init__.py
rename to src/undate/dateformat/__init__.py
diff --git a/undate/dateformat/base.py b/src/undate/dateformat/base.py
similarity index 100%
rename from undate/dateformat/base.py
rename to src/undate/dateformat/base.py
diff --git a/undate/dateformat/iso8601.py b/src/undate/dateformat/iso8601.py
similarity index 100%
rename from undate/dateformat/iso8601.py
rename to src/undate/dateformat/iso8601.py
diff --git a/undate/undate.py b/src/undate/undate.py
similarity index 93%
rename from undate/undate.py
rename to src/undate/undate.py
index 544a1c9..6f83b93 100644
--- a/undate/undate.py
+++ b/src/undate/undate.py
@@ -89,6 +89,14 @@ def duration(self) -> datetime.timedelta:
 
 
 class UndateInterval:
+    """A date range between two uncertain dates.
+
+    :param earliest: Earliest undate
+    :type earliest: `undate.Undate`
+    :param latest: Latest undate
+    :type latest:  `undate.Undate`
+    """
+
     # date range between two uncertain dates
 
     def __init__(
@@ -107,6 +115,11 @@ def __eq__(self, other) -> bool:
         return self.earliest == other.earliest and self.latest == other.latest
 
     def duration(self) -> datetime.timedelta:
+        """Calculate the duration between two undates.
+
+        :returns: A duration
+        :rtype: timedelta
+        """
         # what is the duration of this date range?
 
         # if both years are known, subtract end of range from beginning of start
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..fd54c10
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,39 @@
+[tox]
+envlist = py38, py39, py310, py311
+isolated_build = True
+
+[gh-actions]
+python =
+    3.8: py38
+    3.9: py39
+    3.10: py310
+    3.11: py311
+
+[pytest]
+minversion = 6.0
+addopts = -ra -q
+testpaths =
+    tests
+
+[testenv]
+deps =
+    pytest
+    -e ./[test]
+commands = pytest {posargs}
+
+[testenv:flake8]
+deps =
+    flake8
+commands =
+    flake8 --ignore=E501,E402,F401 src/undate/ tests/
+
+[testenv:docs]
+description = invoke sphinx-build to build the HTML docs
+basepython = python3.10
+deps =
+    -e ./[docs]
+    sphinx
+    sphinx_rtd_theme
+    m2r2
+commands = sphinx-build -d "{toxworkdir}/docs_doctree" docs "{toxworkdir}/docs_out" --color -W -bhtml {posargs}
+           python -c 'import pathlib; print("documentation available under file://\{0\}".format(pathlib.Path(r"{toxworkdir}") / "docs_out" / "index.html"))'


Test patch:
diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml
index 35ec10f..d492d39 100644
--- a/.github/workflows/unit_tests.yml
+++ b/.github/workflows/unit_tests.yml
@@ -21,14 +21,16 @@ jobs:
 
     steps:
       - uses: actions/checkout@v3
-      - name: Set up Python 3.9
+      - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python }}
           cache: 'pip'
-          cache-dependency-path: '**/setup.py'
+          cache-dependency-path: '**/setup.cfg'
       - name: Install package with dependencies
-        run: pip install -e ".[dev]"
+        run: |
+          pip install -e ".[dev]"
+          python -m pip install tox tox-gh-actions
         if: steps.python-cache.outputs.cache-hit != 'true'
       - name: Run unit tests
-        run: pytest
+        run: tox