diff --git a/.cookiecutter/cookiecutter.json b/.cookiecutter/cookiecutter.json
new file mode 100644
index 0000000..27ea8a8
--- /dev/null
+++ b/.cookiecutter/cookiecutter.json
@@ -0,0 +1,24 @@
+{
+ "template": "https://github.com/hypothesis/cookiecutters",
+ "checkout": null,
+ "directory": "pypackage",
+ "ignore": [],
+ "extra_context": {
+ "name": "data-tasks",
+ "package_name": "data_tasks",
+ "slug": "data-tasks",
+ "short_description": "Small library to run data related scripts",
+ "python_versions": "3.10.6, 3.9.13, 3.8.13, 3.7.13",
+ "github_owner": "hypothesis",
+ "copyright_holder": "Hypothesis",
+ "visibility": "public",
+ "console_script": "no",
+ "devdata": "no",
+ "services": "no",
+ "dependabot_pip_interval": "monthly",
+ "__entry_point": "data-tasks",
+ "__github_url": "https://github.com/hypothesis/data-tasks",
+ "__pypi_url": "https://pypi.org/project/data-tasks",
+ "__hdev_project_type": "library"
+ }
+}
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..1c82127
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,9 @@
+version: 2
+updates:
+- package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ day: "sunday"
+ time: "00:00"
+ timezone: "Europe/London"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..d8d4286
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,76 @@
+name: CI
+on:
+ push:
+ workflow_dispatch:
+ workflow_call:
+ schedule:
+ - cron: '0 1 * * *'
+jobs:
+ Format:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - run: python -m pip install tox
+ - run: tox -e checkformatting
+ Lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - run: python -m pip install tox
+ - run: tox -e lint
+ Tests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ['3.10', '3.9', '3.8', '3.7']
+ name: Unit tests with Python ${{ matrix.python-version }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - run: python -m pip install tox
+ - run: tox -e tests
+ - name: Upload coverage file
+ uses: actions/upload-artifact@v3
+ with:
+ name: coverage
+ path: .coverage.*
+ Coverage:
+ needs: tests
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - name: Download coverage files
+ uses: actions/download-artifact@v3
+ with:
+ name: coverage
+ - run: python -m pip install tox
+ - run: tox -e coverage
+ Functests:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: ['3.10', '3.9', '3.8', '3.7']
+ name: Functional tests with Python ${{ matrix.python-version }}
+ steps:
+ - uses: actions/checkout@v3
+ - name: Install Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - run: python -m pip install tox
+ - run: tox -e functests
diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml
new file mode 100644
index 0000000..fb38e52
--- /dev/null
+++ b/.github/workflows/pypi.yml
@@ -0,0 +1,8 @@
+name: PyPI
+on:
+ release:
+ types: [published]
+jobs:
+ PyPI:
+ uses: hypothesis/workflows/.github/workflows/pypi.yml@main
+ secrets: inherit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9709d35
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+build
+coverage
+node_modules
+yarn-error.log
+.coverage*
+.tox
+*.egg-info
+*.pyc
+supervisord.log
+supervisord.pid
+.DS_Store
+.devdata.env
diff --git a/.python-version b/.python-version
new file mode 100644
index 0000000..f1e2c19
--- /dev/null
+++ b/.python-version
@@ -0,0 +1,4 @@
+3.10.6
+3.9.13
+3.8.13
+3.7.13
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3741268
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2022, Hypothesis
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b3867f0
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,98 @@
+comma := ,
+
+.PHONY: help
+help = help::; @echo $$$$(tput bold)$(strip $(1)):$$$$(tput sgr0) $(strip $(2))
+$(call help,make help,print this help message)
+
+.PHONY: services
+
+.PHONY: devdata
+
+.PHONY: shell
+$(call help,make shell,"launch a Python shell in this project's virtualenv")
+shell: python
+ @pyenv exec tox -qe dev
+
+.PHONY: lint
+$(call help,make lint,"lint the code and print any warnings")
+lint: python
+ @pyenv exec tox -qe lint
+
+.PHONY: format
+$(call help,make format,"format the code")
+format: python
+ @pyenv exec tox -qe format
+
+.PHONY: checkformatting
+$(call help,make checkformatting,"crash if the code isn't correctly formatted")
+checkformatting: python
+ @pyenv exec tox -qe checkformatting
+
+.PHONY: test
+$(call help,make test,"run the unit tests in Python 3.10")
+test: python
+ @pyenv exec tox -qe tests
+
+.PHONY: test-py39
+$(call help,make test-py39,"run the unit tests in Python 3.9")
+test-py39: python
+ @pyenv exec tox -qe py39-tests
+
+.PHONY: test-py38
+$(call help,make test-py38,"run the unit tests in Python 3.8")
+test-py38: python
+ @pyenv exec tox -qe py38-tests
+
+.PHONY: test-py37
+$(call help,make test-py37,"run the unit tests in Python 3.7")
+test-py37: python
+ @pyenv exec tox -qe py37-tests
+
+.PHONY: coverage
+$(call help,make coverage,"run the tests and print the coverage report")
+coverage: python
+ @pyenv exec tox --parallel -qe 'tests,py{39,38,37}-tests,coverage'
+
+.PHONY: functests
+$(call help,make functests,"run the functional tests in Python 3.10")
+functests: python
+ @pyenv exec tox -qe functests
+
+.PHONY: functests-py39
+$(call help,make functests-py39,"run the functional tests in Python 3.9")
+functests-py39: python
+ @pyenv exec tox -qe py39-functests
+
+.PHONY: functests-py38
+$(call help,make functests-py38,"run the functional tests in Python 3.8")
+functests-py38: python
+ @pyenv exec tox -qe py38-functests
+
+.PHONY: functests-py37
+$(call help,make functests-py37,"run the functional tests in Python 3.7")
+functests-py37: python
+ @pyenv exec tox -qe py37-functests
+
+.PHONY: sure
+$(call help,make sure,"make sure that the formatting$(comma) linting and tests all pass")
+sure: python
+sure:
+ @pyenv exec tox --parallel -qe 'checkformatting,lint,tests,py{39,38,37}-tests,coverage,functests,py{39,38,37}-functests'
+
+.PHONY: template
+$(call help,make template,"update from the latest cookiecutter template")
+template: python
+ @pyenv exec tox -e template -- $$(if [ -n "$${template+x}" ]; then echo "--template $$template"; fi) $$(if [ -n "$${checkout+x}" ]; then echo "--checkout $$checkout"; fi) $$(if [ -n "$${directory+x}" ]; then echo "--directory $$directory"; fi)
+
+.PHONY: clean
+$(call help,make clean,"delete temporary files etc")
+clean:
+ @rm -rf build dist .tox
+ @find . -path '*/__pycache__*' -delete
+ @find . -path '*.egg-info*' -delete
+
+.PHONY: python
+python:
+ @bin/make_python
+
+-include data_tasks.mk
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b785dab
--- /dev/null
+++ b/README.md
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+# data-tasks
+
+Small library to run data related scripts
+
+## Setting up Your data-tasks Development Environment
+
+First you'll need to install:
+
+* [Git](https://git-scm.com/).
+ On Ubuntu: `sudo apt install git`, on macOS: `brew install git`.
+* [GNU Make](https://www.gnu.org/software/make/).
+ This is probably already installed, run `make --version` to check.
+* [pyenv](https://github.com/pyenv/pyenv).
+ Follow the instructions in pyenv's README to install it.
+ The **Homebrew** method works best on macOS.
+ The **Basic GitHub Checkout** method works best on Ubuntu.
+ You _don't_ need to set up pyenv's shell integration ("shims"), you can
+ [use pyenv without shims](https://github.com/pyenv/pyenv#using-pyenv-without-shims).
+
+Then to set up your development environment:
+
+```terminal
+git clone https://github.com/hypothesis/data-tasks.git
+cd data-tasks
+make help
+```
+
+## Releasing a New Version of the Project
+
+1. First, to get PyPI publishing working you need to go to:
+
+ and add data-tasks to the `PYPI_TOKEN` secret's selected
+ repositories.
+
+2. Now that the data-tasks project has access to the `PYPI_TOKEN` secret
+ you can release a new version by just [creating a new GitHub release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository).
+ Publishing a new GitHub release will automatically trigger
+ [a GitHub Actions workflow](.github/workflows/pypi.yml)
+ that will build the new version of your Python package and upload it to
+ .
+
+## Changing the Project's Python Versions
+
+To change what versions of Python the project uses:
+
+1. Change the Python versions in the
+ [cookiecutter.json](.cookiecutter/cookiecutter.json) file. For example:
+
+ ```json
+ "python_versions": "3.10.4, 3.9.12",
+ ```
+
+2. Re-run the cookiecutter template:
+
+ ```terminal
+ make template
+ ```
+
+3. Commit everything to git and send a pull request
+
+## Changing the Project's Python Dependencies
+
+To change the production dependencies in the `setup.cfg` file:
+
+1. Change the dependencies in the [`.cookiecutter/includes/setuptools/install_requires`](.cookiecutter/includes/setuptools/install_requires) file.
+ If this file doesn't exist yet create it and add some dependencies to it.
+ For example:
+
+ ```
+ pyramid
+ sqlalchemy
+ celery
+ ```
+
+2. Re-run the cookiecutter template:
+
+ ```terminal
+ make template
+ ```
+
+3. Commit everything to git and send a pull request
+
+To change the project's formatting, linting and test dependencies:
+
+1. Change the dependencies in the [`.cookiecutter/includes/tox/deps`](.cookiecutter/includes/tox/deps) file.
+ If this file doesn't exist yet create it and add some dependencies to it.
+ Use tox's [factor-conditional settings](https://tox.wiki/en/latest/config.html#factors-and-factor-conditional-settings)
+ to limit which environment(s) each dependency is used in.
+ For example:
+
+ ```
+ lint: flake8,
+ format: autopep8,
+ lint,tests: pytest-faker,
+ ```
+
+2. Re-run the cookiecutter template:
+
+ ```terminal
+ make template
+ ```
+
+3. Commit everything to git and send a pull request
diff --git a/bin/make_python b/bin/make_python
new file mode 100755
index 0000000..eb1cc07
--- /dev/null
+++ b/bin/make_python
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# Ensure that each version of Python in the .python_version file is installed
+# in pyenv and that tox is installed within each version of Python.
+set -euo pipefail
+
+if [ -n "${CI+x}" ]; then exit; fi
+
+pyenv_root=$(pyenv root)
+
+for python_version in 3.10.6 3.9.13 3.8.13 3.7.13; do
+ bin_dir=$pyenv_root/versions/$python_version/bin
+ if [ ! -f "$bin_dir"/tox ]; then
+ pyenv install --skip-existing "$python_version"
+ "$bin_dir"/pip install --disable-pip-version-check tox
+ pyenv rehash
+ fi
+done
diff --git a/bin/make_template b/bin/make_template
new file mode 100755
index 0000000..3eb117a
--- /dev/null
+++ b/bin/make_template
@@ -0,0 +1,32 @@
+#!/usr/bin/env python3
+import argparse
+import json
+import os
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from cookiecutter.main import cookiecutter
+
+with open(".cookiecutter/cookiecutter.json", "r", encoding="utf-8") as cookiecutter_json_file:
+ config = json.loads(cookiecutter_json_file.read())
+
+extra_context = config.get("extra_context", {})
+extra_context["__ignore__"] = config.get("ignore", [])
+extra_context["__target_dir__"] = Path(os.getcwd())
+
+parser = argparse.ArgumentParser(description="Update the project from the cookiecutter template")
+parser.add_argument("--template", help="the cookiecutter template to use (default: what's in cookiecutter.json)")
+parser.add_argument("--checkout", help="the branch, tag or commit of the cookiecutter template to use (default: what's in cookiecutter.json)")
+parser.add_argument("--directory", help="the directory within the cookiecutter repo to use to use (default: what's in cookiecutter.json)")
+args = parser.parse_args()
+
+with TemporaryDirectory() as tmpdirname:
+ cookiecutter(
+ template=args.template or config.get("template"),
+ checkout=args.checkout or config.get("checkout"),
+ directory=args.directory or config.get("directory"),
+ extra_context=extra_context,
+ no_input=True,
+ overwrite_if_exists=True,
+ output_dir=tmpdirname,
+ )
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..e5c468e
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,138 @@
+[build-system]
+requires = ["setuptools>=45", "setuptools_scm[toml]>=6.2"]
+build-backend = "setuptools.build_meta"
+
+[tool.setuptools_scm]
+
+[tool.pytest.ini_options]
+addopts = "-q"
+filterwarnings = [
+ "error", # Fail the tests if there are any warnings.
+ "ignore:^find_module\\(\\) is deprecated and slated for removal in Python 3.12; use find_spec\\(\\) instead$:DeprecationWarning:importlib",
+ "ignore:^FileFinder.find_loader\\(\\) is deprecated and slated for removal in Python 3.12; use find_spec\\(\\) instead$:DeprecationWarning:importlib",
+]
+
+[tool.pydocstyle]
+ignore = [
+ # Missing docstrings.
+ "D100","D101","D102","D103","D104","D105","D106","D107",
+
+ # "No blank lines allowed after function docstring" conflicts with the
+ # Black code formatter which insists on inserting blank lines after
+ # function docstrings.
+ "D202",
+
+ # "1 blank line required before class docstring" conflicts with another
+ # pydocstyle rule D211 "No blank lines allowed before class docstring".
+ "D203",
+
+ # "Multi-line docstring summary should start at the first line"
+ # and "Multi-line docstring summary should start at the second line".
+ # These two rules conflict with each other so you have to disable one of them.
+ # How about we disable them both? PEP 257 says either approach is okay:
+ #
+ # > The summary line may be on the same line as the opening quotes or on
+ # > the next line.
+ # >
+ # > https://peps.python.org/pep-0257/#multi-line-docstrings
+ "D212",
+ "D213",
+]
+
+[tool.coverage.run]
+branch = true
+parallel = true
+source = ["data_tasks", "tests/unit"]
+omit = [
+ "*/data_tasks/__main__.py",
+]
+
+[tool.coverage.paths]
+source = ["src", ".tox/*tests/lib/python*/site-packages"]
+
+[tool.coverage.report]
+show_missing = true
+precision = 2
+fail_under = 100.00
+skip_covered = true
+
+[tool.isort]
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+line_length = 88
+default_section = "THIRDPARTY"
+known_first_party = ["data_tasks", "tests"]
+
+[tool.pylint.main]
+jobs = 0 # Use one process for CPU.
+
+load-plugins = [
+ "pylint.extensions.bad_builtin",
+ "pylint.extensions.check_elif",
+ "pylint.extensions.comparetozero",
+ "pylint.extensions.docparams",
+ "pylint.extensions.emptystring",
+ "pylint.extensions.mccabe",
+ "pylint.extensions.overlapping_exceptions",
+ "pylint.extensions.redefined_variable_type",
+]
+
+# Fail if there are *any* messages from PyLint.
+# The letters refer to PyLint's message categories, see
+# https://pylint.pycqa.org/en/latest/messages/messages_introduction.html
+fail-on = ["C", "E", "F", "I", "R", "W"]
+
+[tool.pylint.messages_control]
+enable = [
+ "bad-inline-option",
+ "deprecated-pragma",
+ "useless-suppression",
+ "use-symbolic-message-instead",
+]
+disable = [
+ # Docstrings are encouraged but we don't want to enforce that everything
+ # must have a docstring.
+ "missing-docstring",
+
+ # We don't always want to have to put a `:return:` in a docstring.
+ "missing-return-doc",
+
+ # We don't always want to have to put an `:rtype:` in a docstring.
+ "missing-return-type-doc",
+
+ # We don't want to have to document the type of every parameter with a
+ # `:type:` in the docstring.
+ "missing-type-doc",
+
+ # We use isort to sort and group our imports, so we don't need PyLint to
+ # check them for us.
+ "ungrouped-imports",
+
+ # We use Black to format our code automatically, so we don't need PyLint to
+ # check formatting for us.
+ "line-too-long",
+
+ # We use isort to sort out imports so we don't need PyLint to check import
+ # ordering for us.
+ "wrong-import-order",
+
+ "too-few-public-methods",
+
+ # Issues to disable this for false positives, disabling it globally in the meantime https://github.com/PyCQA/pylint/issues/214
+ "duplicate-code",
+]
+
+good-names = [
+ "i", "j", "k", "ex", "Run", "_", # PyLint's default good names.
+ "tm", "db", "ai",
+]
+
+[tool.pylint.reports]
+output-format = "colorized"
+score = "no"
+
+[tool.hdev]
+project_name = "data-tasks"
+project_type = "library"
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..41775b7
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,42 @@
+[metadata]
+name = data-tasks
+description = Small library to run data related scripts
+long_description = file: README.md
+long_description_content_type = text/markdown
+url = https://github.com/hypothesis/data-tasks
+project_urls =
+ Bug Tracker = https://github.com/hypothesis/data-tasks/issues
+ Changelog = https://github.com/hypothesis/data-tasks/releases
+classifiers =
+ Programming Language :: Python :: 3
+ License :: OSI Approved :: BSD License
+ Intended Audience :: Developers
+
+[options]
+package_dir =
+ = src
+packages = find:
+python_requires = >=3.7
+install_requires =
+ importlib_metadata;python_version<"3.8."
+
+[options.packages.find]
+where = src
+
+[options.entry_points]
+
+[pycodestyle]
+ignore =
+ # Disable pycodestyle errors and warnings that we don't care about because
+ # Black formats our code for us.
+ E203, # Whitespace before ':',
+ E231, # Missing whitespace after ',',
+ E501, # Line too long,
+ W503, # Line break before binary operator,
+
+ # "Comparison to None should be 'if cond is None:'.
+ # PyLint finds these so we don't need pycodestyle to.
+ E711,
+
+ # Bare except. PyLint finds these for us so we don't need pycodestyle to.
+ E722,
diff --git a/src/data_tasks/__init__.py b/src/data_tasks/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/data_tasks/core.py b/src/data_tasks/core.py
new file mode 100644
index 0000000..d1b7a0d
--- /dev/null
+++ b/src/data_tasks/core.py
@@ -0,0 +1,2 @@
+def hello_world():
+ return "Hello, world!"
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/functional/sanity_test.py b/tests/functional/sanity_test.py
new file mode 100644
index 0000000..b76ddae
--- /dev/null
+++ b/tests/functional/sanity_test.py
@@ -0,0 +1,2 @@
+def test_it():
+ assert True
diff --git a/tests/pyproject.toml b/tests/pyproject.toml
new file mode 100644
index 0000000..4b374da
--- /dev/null
+++ b/tests/pyproject.toml
@@ -0,0 +1,92 @@
+[tool.pylint.main]
+jobs = 0 # Use one process for CPU.
+
+load-plugins = [
+ "pylint.extensions.bad_builtin",
+ "pylint.extensions.check_elif",
+ "pylint.extensions.comparetozero",
+ "pylint.extensions.docparams",
+ "pylint.extensions.emptystring",
+ "pylint.extensions.mccabe",
+ "pylint.extensions.overlapping_exceptions",
+ "pylint.extensions.redefined_variable_type",
+]
+
+# Fail if there are *any* messages from PyLint.
+# The letters refer to PyLint's message categories, see
+# https://pylint.pycqa.org/en/latest/messages/messages_introduction.html
+fail-on = ["C", "E", "F", "I", "R", "W"]
+
+[tool.pylint.messages_control]
+ignore-paths = [
+]
+enable = [
+ "bad-inline-option",
+ "deprecated-pragma",
+ "useless-suppression",
+ "use-symbolic-message-instead",
+]
+disable = [
+ # Docstrings are encouraged but we don't want to enforce that everything
+ # must have a docstring.
+ "missing-docstring",
+
+ # We don't always want to have to put a `:return:` in a docstring.
+ "missing-return-doc",
+
+ # We don't always want to have to put an `:rtype:` in a docstring.
+ "missing-return-type-doc",
+
+ # We don't want to have to document the type of every parameter with a
+ # `:type:` in the docstring.
+ "missing-type-doc",
+
+ # We use isort to sort and group our imports, so we don't need PyLint to
+ # check them for us.
+ "ungrouped-imports",
+
+ # We use Black to format our code automatically, so we don't need PyLint to
+ # check formatting for us.
+ "line-too-long",
+
+ # Because of how pytest fixtures work it's frequently necessary for
+ # parameters to redefine outer names.
+ "redefined-outer-name",
+
+ # Lots of test methods don't use self, but we still want to group our tests
+ # into classes.
+ "too-few-public-methods",
+ "too-many-public-methods",
+
+ "too-many-arguments",
+
+ # not-callable is mis-firing on all pytest.mark.parametrize usages, so
+ # disable it for now. This can be re-enabled once a new pytest version
+ # including https://github.com/pytest-dev/pytest/pull/7565 has been
+ # released.
+ "not-callable",
+
+ # Issues to disable this for false positives, disabling it globally in the meantime https://github.com/PyCQA/pylint/issues/214
+ "duplicate-code",
+]
+
+# Just disable PyLint's name style checking for the tests, because we
+# frequently use lots of argument names that don't conform.
+# For example we frequently create pytest fixtures that aren't named in
+# snake_case, such as a fixture that returns a mock of the FooBar class would
+# be named FooBar in CamelCase.
+argument-naming-style = "any"
+class-naming-style = "any"
+function-naming-style = "any"
+method-naming-style = "any"
+variable-naming-style = "any"
+
+good-names = [
+ "i", "j", "k", "ex", "Run", "_", # PyLint's default good names.
+ "pytestmark",
+ "os", # Name used for mocks of the standard os module.
+]
+
+[tool.pylint.reports]
+output-format = "colorized"
+score = "no"
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/unit/data_tasks/__init__.py b/tests/unit/data_tasks/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/unit/data_tasks/core_test.py b/tests/unit/data_tasks/core_test.py
new file mode 100644
index 0000000..4c4bae4
--- /dev/null
+++ b/tests/unit/data_tasks/core_test.py
@@ -0,0 +1,6 @@
+from data_tasks.core import hello_world
+
+
+class TestHelloWorld:
+ def test_it(self):
+ assert hello_world() == "Hello, world!"
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..dde960f
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,52 @@
+[tox]
+envlist = tests
+minversion = 3.25.0
+requires =
+ tox-envfile
+ tox-faster
+ tox-run-command
+ tox-recreate
+isolated_build = true
+
+[testenv]
+skip_install =
+ format,checkformatting,coverage,template: true
+setenv =
+ PYTHONUNBUFFERED = 1
+ OBJC_DISABLE_INITIALIZE_FORK_SAFETY = YES
+passenv =
+ HOME
+ PYTEST_ADDOPTS
+ dev: DEBUG
+ dev: SENTRY_DSN
+deps =
+ dev: ipython
+ format,checkformatting: black
+ format,checkformatting: isort
+ lint: toml
+ lint: pylint
+ lint: pydocstyle
+ lint: pycodestyle
+ lint,tests: pytest-mock
+ lint,tests,coverage: coverage[toml]
+ lint,tests,functests: pytest
+ lint,tests,functests: factory-boy
+ lint,tests,functests: h-matchers
+ lint,template: cookiecutter
+depends =
+ coverage: tests,py{39,38,37}-tests
+commands =
+ dev: {posargs:ipython --classic --no-banner --no-confirm-exit}
+ format: black src tests bin
+ format: isort --atomic src tests bin
+ checkformatting: black --check src tests bin
+ checkformatting: isort --quiet --check-only src tests bin
+ lint: pylint src bin
+ lint: pylint --rcfile=tests/pyproject.toml tests
+ lint: pydocstyle src tests bin
+ lint: pycodestyle src tests bin
+ tests: coverage run -m pytest --failed-first --new-first --no-header --quiet {posargs:tests/unit/}
+ functests: pytest --failed-first --new-first --no-header --quiet {posargs:tests/functional/}
+ coverage: -coverage combine
+ coverage: coverage report
+ template: python3 bin/make_template {posargs}