From fa3bef2308bfd6bba43f6c383b49ac414de3593e Mon Sep 17 00:00:00 2001 From: Bobby Norton Date: Fri, 22 Mar 2019 12:56:33 -0500 Subject: [PATCH] Initial import --- .gitattributes | 1 + .gitignore | 9 ++ README.md | 83 +++++++++++++ cookiecutter.json | 6 + requirements.txt | 4 + tests/conftest.py | 45 +++++++ tests/test_creation.py | 95 +++++++++++++++ {{ cookiecutter.repo_name }}/.gitignore | 89 ++++++++++++++ {{ cookiecutter.repo_name }}/Makefile | 111 ++++++++++++++++++ {{ cookiecutter.repo_name }}/README.md | 49 ++++++++ {{ cookiecutter.repo_name }}/models/.gitkeep | 0 .../notebooks/.gitkeep | 0 .../references/.gitkeep | 0 {{ cookiecutter.repo_name }}/reports/.gitkeep | 0 .../reports/figures/.gitkeep | 0 {{ cookiecutter.repo_name }}/requirements.txt | 21 ++++ {{ cookiecutter.repo_name }}/setup.py | 16 +++ {{ cookiecutter.repo_name }}/src/__init__.py | 0 .../src/__version__.py | 1 + .../src/data/.gitkeep | 0 .../src/data/__init__.py | 0 .../src/data/make_dataset.py | 30 +++++ .../src/features/.gitkeep | 0 .../src/features/__init__.py | 0 .../src/features/build_features.py | 0 .../src/models/.gitkeep | 0 .../src/models/__init__.py | 0 .../src/models/predict_model.py | 0 .../src/models/train_model.py | 0 .../src/visualization/.gitkeep | 0 .../src/visualization/__init__.py | 0 .../src/visualization/visualize.py | 0 .../test_python_version.py | 17 +++ 33 files changed, 577 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cookiecutter.json create mode 100644 requirements.txt create mode 100644 tests/conftest.py create mode 100644 tests/test_creation.py create mode 100644 {{ cookiecutter.repo_name }}/.gitignore create mode 100644 {{ cookiecutter.repo_name }}/Makefile create mode 100644 {{ cookiecutter.repo_name }}/README.md create mode 100644 {{ cookiecutter.repo_name }}/models/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/notebooks/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/references/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/reports/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/reports/figures/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/requirements.txt create mode 100644 {{ cookiecutter.repo_name }}/setup.py create mode 100644 {{ cookiecutter.repo_name }}/src/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/src/__version__.py create mode 100644 {{ cookiecutter.repo_name }}/src/data/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/src/data/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/src/data/make_dataset.py create mode 100644 {{ cookiecutter.repo_name }}/src/features/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/src/features/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/src/features/build_features.py create mode 100644 {{ cookiecutter.repo_name }}/src/models/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/src/models/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/src/models/predict_model.py create mode 100644 {{ cookiecutter.repo_name }}/src/models/train_model.py create mode 100644 {{ cookiecutter.repo_name }}/src/visualization/.gitkeep create mode 100644 {{ cookiecutter.repo_name }}/src/visualization/__init__.py create mode 100644 {{ cookiecutter.repo_name }}/src/visualization/visualize.py create mode 100644 {{ cookiecutter.repo_name }}/test_python_version.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..176a458 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb9b79f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +docs/site/ + +# OSX Junk +.DS_Store + +# test cache +.cache/* +tests/__pycache__/* +*.pytest_cache/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..16af8be --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# Tested Minds Data Science Template + + +_A logical, reasonably standardized, but flexible project structure for doing and sharing data science work._ + + +This is a customized [cookiecutter](https://cookiecutter.readthedocs.io/en/latest/readme.html) version of the [cookiecutter-data-science](http://drivendata.github.io/cookiecutter-data-science/) project from [DrivenData](https://www.drivendata.org/). + + +#### [Documentation](http://drivendata.github.io/cookiecutter-data-science/) + + +### Requirements to use the cookiecutter template: + +- Python 3.x +- [Cookiecutter Python package](http://cookiecutter.readthedocs.org/en/latest/installation.html) >= 1.4.0 + +``` bash +pip install cookiecutter +``` + + +### To start a new project, run: + + cookiecutter https://github.com/testedminds/data-science-template + + + +### The resulting directory structure + +The directory structure of your new project looks like this: + +``` +├── Makefile <- Makefile with commands like `make data` or `make train` +├── README.md <- The top-level README for developers using this project. +├── data +│ ├── external <- Data from third-party sources. +│ ├── interim <- Intermediate data that has been transformed. +│ ├── processed <- The final, canonical data sets for modeling. +│ └── raw <- The original, immutable data dump. +│ +├── docs <- A default Sphinx project; see sphinx-doc.org for details +│ +├── models <- Trained and serialized models, model predictions, or model summaries +│ +├── notebooks <- Jupyter notebooks. Naming convention is a number (for ordering) +│ and a short `-` delimited description, e.g. `1.0-initial-data-exploration`. +│ +├── references <- Data dictionaries, manuals, and all other explanatory materials. +│ +├── reports <- Generated analysis as HTML, PDF, LaTeX, etc. +│ └── figures <- Generated graphics and figures to be used in reporting +│ +├── requirements.txt <- The requirements file for reproducing the analysis environment, e.g. +│ generated with `pip freeze > requirements.txt` +│ +└── src <- Source code for use in this project. + ├── __init__.py <- Makes src a Python module + │ + ├── data <- Scripts to download or generate data + │ └── make_dataset.py + │ + ├── features <- Scripts to turn raw data into features for modeling + │ └── build_features.py + │ + ├── models <- Scripts to train models and then use trained models to make + │ │ predictions + │ ├── predict_model.py + │ └── train_model.py + │ + └── visualization <- Scripts to create exploratory and results oriented visualizations + └── visualize.py +``` + + +### Installing development requirements + + pip install -r requirements.txt + + +### Running the tests + + py.test tests diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..c546901 --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,6 @@ +{ + "project_name": "project_name", + "repo_name": "{{ cookiecutter.project_name.lower().replace(' ', '_') }}", + "author_name": "Your name (or your organization/company/team)", + "description": "A short description of the project." +} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..07f48f4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +mkdocs +mkdocs-cinder +cookiecutter +pytest diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..9bf05d8 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,45 @@ +import sys +import pytest +import shutil +from pathlib import Path +from cookiecutter import main + +CCDS_ROOT = Path(__file__).parents[1].resolve() + +args = { + 'project_name': 'TestedMinds', + 'author_name': 'TestedMinds', + } + + +def system_check(basename): + platform = sys.platform + if 'linux' in platform: + basename = basename.lower() + return basename + + +@pytest.fixture(scope='class', params=[{}, args]) +def default_baked_project(tmpdir_factory, request): + temp = tmpdir_factory.mktemp('data-project') + out_dir = Path(temp).resolve() + + pytest.param = request.param + main.cookiecutter( + str(CCDS_ROOT), + no_input=True, + extra_context=pytest.param, + output_dir=out_dir + ) + + pn = pytest.param.get('project_name') or 'project_name' + + # project name gets converted to lower case on Linux but not Mac + pn = system_check(pn) + + proj = out_dir / pn + request.cls.path = proj + yield + + # cleanup after + shutil.rmtree(out_dir) diff --git a/tests/test_creation.py b/tests/test_creation.py new file mode 100644 index 0000000..1de1032 --- /dev/null +++ b/tests/test_creation.py @@ -0,0 +1,95 @@ +import os +import pytest +from subprocess import check_output +from conftest import system_check, args + + +def no_curlies(filepath): + """ Utility to make sure no curly braces appear in a file. + That is, was jinja able to render everthing? + """ + with open(filepath, 'r') as f: + data = f.read() + + template_strings = [ + '{{', + '}}', + '{%', + '%}' + ] + + template_strings_in_file = [s in data for s in template_strings] + return not any(template_strings_in_file) + + +@pytest.mark.usefixtures("default_baked_project") +class TestCookieSetup(object): + + def test_project_name(self): + project = self.path + if pytest.param.get('project_name'): + name = system_check(args['project_name']) + assert project.name == name + else: + assert project.name == 'project_name' + + def test_author(self): + setup_ = self.path / 'setup.py' + setup_args = ['python', setup_, '--author'] + p = check_output(setup_args).decode('ascii').strip() + if pytest.param.get('author_name'): + assert p == args['author_name'] + else: + assert p == 'Your name (or your organization/company/team)' + + def test_readme(self): + readme_path = self.path / 'README.md' + assert readme_path.exists() + assert no_curlies(readme_path) + if pytest.param.get('project_name'): + with open(readme_path) as fin: + assert next(fin).strip() == args['project_name'] + + def test_setup(self): + setup_ = self.path / 'setup.py' + setup_args = ['python', setup_, '--version'] + p = check_output(setup_args).decode('ascii').strip() + assert p == '0.1.0' + + def test_requirements(self): + reqs_path = self.path / 'requirements.txt' + assert reqs_path.exists() + assert no_curlies(reqs_path) + + def test_makefile(self): + makefile_path = self.path / 'Makefile' + assert makefile_path.exists() + assert no_curlies(makefile_path) + + def test_folders(self): + expected_dirs = [ + 'data', + 'data/external', + 'data/interim', + 'data/processed', + 'data/raw', + 'models', + 'notebooks', + 'references', + 'reports', + 'reports/figures', + 'src', + 'src/data', + 'src/features', + 'src/models', + 'src/visualization', + ] + + ignored_dirs = [ + str(self.path) + ] + + abs_expected_dirs = [str(self.path / d) for d in expected_dirs] + abs_dirs, _, _ = list(zip(*os.walk(self.path))) + assert len(set(abs_expected_dirs + ignored_dirs) - set(abs_dirs)) == 0 + diff --git a/{{ cookiecutter.repo_name }}/.gitignore b/{{ cookiecutter.repo_name }}/.gitignore new file mode 100644 index 0000000..d7c9832 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/.gitignore @@ -0,0 +1,89 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# DotEnv configuration +.env + +# Database +*.db +*.rdb + +# Pycharm +.idea + +# VS Code +.vscode/ + +# Spyder +.spyproject/ + +# Jupyter NB Checkpoints +.ipynb_checkpoints/ + +# exclude data from source control by default +/data/ + +# Mac OS-specific storage files +.DS_Store + +# vim +*.swp +*.swo + +# Mypy cache +.mypy_cache/ diff --git a/{{ cookiecutter.repo_name }}/Makefile b/{{ cookiecutter.repo_name }}/Makefile new file mode 100644 index 0000000..affdaf9 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/Makefile @@ -0,0 +1,111 @@ +.PHONY: clean data lint requirements + +################################################################################# +# GLOBALS # +################################################################################# + +PROJECT_DIR := $(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) + +PROJECT_NAME = {{ cookiecutter.repo_name }} + +################################################################################# +# COMMANDS # +################################################################################# + +## Install Python Dependencies +requirements: test_environment + python -m pip install -U pip setuptools wheel + python -m pip install -r requirements.txt + +## Make Dataset +data: requirements + python src/data/make_dataset.py + +## Delete all compiled Python files +clean: + find . -type f -name "*.py[co]" -delete + find . -type d -name "__pycache__" -delete + +## Lint using flake8 +lint: + flake8 src + +## Set up python interpreter environment +create_environment: + python -m pip install -q virtualenv virtualenvwrapper + @echo ">>> Installing virtualenvwrapper if not already intalled.\nMake sure the following lines are in shell startup file\n\ + export WORKON_HOME=$$HOME/.virtualenvs\nexport PROJECT_HOME=$$HOME/Devel\nsource /usr/local/bin/virtualenvwrapper.sh\n" + @bash -c "source `which virtualenvwrapper.sh`;mkvirtualenv $(PROJECT_NAME)" + @echo ">>> New virtualenv created. Activate with:\nworkon $(PROJECT_NAME)" + + +## Test python environment is setup correctly +test_environment: + python test_environment.py + +################################################################################# +# PROJECT RULES # +################################################################################# + + + +################################################################################# +# Self Documenting Commands # +################################################################################# + +.DEFAULT_GOAL := help + +# Inspired by +# sed script explained: +# /^##/: +# * save line in hold space +# * purge line +# * Loop: +# * append newline + line to hold space +# * go to next line +# * if line starts with doc comment, strip comment character off and loop +# * remove target prerequisites +# * append hold space (+ newline) to line +# * replace newline plus comments by `---` +# * print line +# Separate expressions are necessary because labels cannot be delimited by +# semicolon; see +.PHONY: help +help: + @echo "$$(tput bold)Available rules:$$(tput sgr0)" + @echo + @sed -n -e "/^## / { \ + h; \ + s/.*//; \ + :doc" \ + -e "H; \ + n; \ + s/^## //; \ + t doc" \ + -e "s/:.*//; \ + G; \ + s/\\n## /---/; \ + s/\\n/ /g; \ + p; \ + }" ${MAKEFILE_LIST} \ + | LC_ALL='C' sort --ignore-case \ + | awk -F '---' \ + -v ncol=$$(tput cols) \ + -v indent=19 \ + -v col_on="$$(tput setaf 6)" \ + -v col_off="$$(tput sgr0)" \ + '{ \ + printf "%s%*s%s ", col_on, -indent, $$1, col_off; \ + n = split($$2, words, " "); \ + line_length = ncol - indent; \ + for (i = 1; i <= n; i++) { \ + line_length -= length(words[i]) + 1; \ + if (line_length <= 0) { \ + line_length = ncol - indent - length(words[i]) - 1; \ + printf "\n%*s ", -indent, " "; \ + } \ + printf "%s ", words[i]; \ + } \ + printf "\n"; \ + }' \ + | more $(shell test $(shell uname) = Darwin && echo '--no-init --raw-control-chars') diff --git a/{{ cookiecutter.repo_name }}/README.md b/{{ cookiecutter.repo_name }}/README.md new file mode 100644 index 0000000..e43cdb7 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/README.md @@ -0,0 +1,49 @@ +{{cookiecutter.project_name}} +============================== + +{{cookiecutter.description}} + +### Project Organization + + ├── Makefile <- Makefile with commands like `make data` or `make train` + ├── README.md <- The top-level README for developers using this project. + ├── data + │ ├── external <- Data from third-party sources. + │ ├── interim <- Intermediate data that has been transformed. + │ ├── processed <- The final, canonical data sets for modeling. + │ └── raw <- The original, immutable data dump. + │ + ├── docs <- A default Sphinx project; see sphinx-doc.org for details + │ + ├── models <- Trained and serialized models, model predictions, or model summaries + │ + ├── notebooks <- Jupyter notebooks. Naming convention is a number (for ordering) + │ and a short `-` delimited description, e.g. `1.0-initial-data-exploration`. + │ + ├── references <- Data dictionaries, manuals, and all other explanatory materials. + │ + ├── reports <- Generated analysis as HTML, PDF, LaTeX, etc. + │ └── figures <- Generated graphics and figures to be used in reporting + │ + ├── requirements.txt <- The requirements file for reproducing the analysis environment, e.g. + │ generated with `pip freeze > requirements.txt` + │ + └── src <- Source code for use in this project. + ├── __init__.py <- Makes src a Python module + │ + ├── data <- Scripts to download or generate data + │ └── make_dataset.py + │ + ├── features <- Scripts to turn raw data into features for modeling + │ └── build_features.py + │ + ├── models <- Scripts to train models and then use trained models to make + │ │ predictions + │ ├── predict_model.py + │ └── train_model.py + │ + └── visualization <- Scripts to create exploratory and results oriented visualizations + └── visualize.py + + +

Project based on the Tested Minds data science project template.

diff --git a/{{ cookiecutter.repo_name }}/models/.gitkeep b/{{ cookiecutter.repo_name }}/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/notebooks/.gitkeep b/{{ cookiecutter.repo_name }}/notebooks/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/references/.gitkeep b/{{ cookiecutter.repo_name }}/references/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/reports/.gitkeep b/{{ cookiecutter.repo_name }}/reports/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/reports/figures/.gitkeep b/{{ cookiecutter.repo_name }}/reports/figures/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/requirements.txt b/{{ cookiecutter.repo_name }}/requirements.txt new file mode 100644 index 0000000..d1d5bf7 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/requirements.txt @@ -0,0 +1,21 @@ +--index-url https://pypi.python.org/simple/ + +# local package +-e . + +# external requirements +click +Sphinx +coverage +flake8 +python-dotenv>=0.5.1 +pytest + +beautifulsoup4 +graphviz +jupyter +pandas +sand +scikit-learn +seaborn +toolz diff --git a/{{ cookiecutter.repo_name }}/setup.py b/{{ cookiecutter.repo_name }}/setup.py new file mode 100644 index 0000000..1dd3bc5 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/setup.py @@ -0,0 +1,16 @@ +from setuptools import find_packages, setup +from pathlib import Path + + +# Read __version__ programmatically to avoid loading dependencies from src/__init__.py +dir = Path(__file__).resolve().parent + +exec(open(dir / 'src/__version__.py').read()) + +setup( + name='src', + packages=find_packages(), + version=__version__, + description='{{ cookiecutter.description }}', + author='{{ cookiecutter.author_name }}', +) diff --git a/{{ cookiecutter.repo_name }}/src/__init__.py b/{{ cookiecutter.repo_name }}/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/__version__.py b/{{ cookiecutter.repo_name }}/src/__version__.py new file mode 100644 index 0000000..b794fd4 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/__version__.py @@ -0,0 +1 @@ +__version__ = '0.1.0' diff --git a/{{ cookiecutter.repo_name }}/src/data/.gitkeep b/{{ cookiecutter.repo_name }}/src/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/data/__init__.py b/{{ cookiecutter.repo_name }}/src/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/data/make_dataset.py b/{{ cookiecutter.repo_name }}/src/data/make_dataset.py new file mode 100644 index 0000000..96b377a --- /dev/null +++ b/{{ cookiecutter.repo_name }}/src/data/make_dataset.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +import click +import logging +from pathlib import Path +from dotenv import find_dotenv, load_dotenv + + +@click.command() +@click.argument('input_filepath', type=click.Path(exists=True)) +@click.argument('output_filepath', type=click.Path()) +def main(input_filepath, output_filepath): + """ Runs data processing scripts to turn raw data from (../raw) into + cleaned data ready to be analyzed (saved in ../processed). + """ + logger = logging.getLogger(__name__) + logger.info('making final data set from raw data') + + +if __name__ == '__main__': + log_fmt = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + logging.basicConfig(level=logging.INFO, format=log_fmt) + + # not used in this stub but often useful for finding various files + project_dir = Path(__file__).resolve().parents[2] + + # find .env automagically by walking up directories until it's found, then + # load up the .env entries as environment variables + load_dotenv(find_dotenv()) + + main() diff --git a/{{ cookiecutter.repo_name }}/src/features/.gitkeep b/{{ cookiecutter.repo_name }}/src/features/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/features/__init__.py b/{{ cookiecutter.repo_name }}/src/features/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/features/build_features.py b/{{ cookiecutter.repo_name }}/src/features/build_features.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/models/.gitkeep b/{{ cookiecutter.repo_name }}/src/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/models/__init__.py b/{{ cookiecutter.repo_name }}/src/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/models/predict_model.py b/{{ cookiecutter.repo_name }}/src/models/predict_model.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/models/train_model.py b/{{ cookiecutter.repo_name }}/src/models/train_model.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/visualization/.gitkeep b/{{ cookiecutter.repo_name }}/src/visualization/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/visualization/__init__.py b/{{ cookiecutter.repo_name }}/src/visualization/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/src/visualization/visualize.py b/{{ cookiecutter.repo_name }}/src/visualization/visualize.py new file mode 100644 index 0000000..e69de29 diff --git a/{{ cookiecutter.repo_name }}/test_python_version.py b/{{ cookiecutter.repo_name }}/test_python_version.py new file mode 100644 index 0000000..09745a5 --- /dev/null +++ b/{{ cookiecutter.repo_name }}/test_python_version.py @@ -0,0 +1,17 @@ +import sys + + +def main(): + system_major = sys.version_info.major + required_major = 3 + + if system_major != required_major: + raise TypeError( + "This project requires Python {}. Found: Python {}".format( + required_major, sys.version)) + else: + print(">>> Development environment passes all tests!") + + +if __name__ == '__main__': + main()