Last active
February 10, 2017 13:09
-
-
Save astromechza/4a3fce3b89928db42ffa to your computer and use it in GitHub Desktop.
pyskeleton.py: Generate a basic Python package, ready for development.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
This script creates a basic Python package skeleton in the given directory. | |
It also sets up pytest (with coverage). | |
""" | |
import os | |
import re | |
import sys | |
import argparse | |
NAME_REGEX = re.compile(r'[a-z][a-z0-9_]+') | |
GITIGNORE_CONTENT = """/.build | |
/build/ | |
/.dist | |
/dist/ | |
*.pyc | |
/.eggs | |
*.egg-info | |
*.egg/ | |
.cache/ | |
/htmlcov | |
.coverage | |
/.idea/ | |
/venv*/ | |
""" | |
SETUPPY_CONTENT = """\ | |
from setuptools import setup, find_packages | |
setup( | |
# package info | |
name='{name}', | |
version='0.1', | |
description='', | |
packages=find_packages(exclude=['tests', 'tests.*']), | |
# runtime scripts | |
scripts=[], | |
# alternate (preferable form) | |
entry_points={{ | |
'console_scripts': [ | |
'name_of_executable = module.with:function_to_execute' | |
] | |
}}, | |
# run time requirements | |
# exact versions are in the requirements.txt file | |
install_requires=[], | |
# tests dependencies are listed in the test_requirements.txt file | |
) | |
""" | |
TEST_CONTENT = """import {package_name} | |
\"\"\" | |
Run these tests using py.test from the command line. | |
$ py.test | |
Remember to do this first: | |
$ python setup.py develop | |
$ pip install -r requirements.txt | |
$ pip install -r test_requirements.txt | |
\"\"\" | |
# simple way of doing tests | |
def test_blank(): | |
assert True | |
from unittest import TestCase | |
def TestComplex(TestCase): | |
def test_the_thing(self): | |
self.assertIn(1, [1, 2, 3]) | |
""" | |
SETUPCFG_CONTENT = """[tool:pytest] | |
addopts = tests --tb=short --cov={package_name} --cov-report=term-missing --cov-report=html | |
""" | |
README_CONTENT = """# `{name}` - Readme | |
The following instructions have been tested on Python 2.7 and 3.5. | |
## How to install | |
``` | |
$ python setup.py install | |
``` | |
## How to setup for development | |
``` | |
$ virtualenv --no-site-packages venv | |
$ source venv/bin/activate | |
$ pip install -r requirements.txt | |
$ pip install -r test_requirements.txt | |
$ python setup.py develop | |
``` | |
And then run the tests with: | |
``` | |
$ py.test | |
``` | |
""" | |
def build_package_name(name): | |
name = name.lower() | |
package_name = re.sub(r'[^a-z\_0-9]+', '_', name) | |
if not re.match(r'^[a-z][a-z0-9\_]+$', package_name): | |
raise Exception("Bad package name %s" % package_name) | |
return package_name | |
def grey_wrap(content): | |
return "\033[37m{}\033[39m".format(content) | |
def green_wrap(content): | |
return "\033[32m{}\033[39m".format(content) | |
def blue_wrap(content): | |
return "\033[36m{}\033[39m".format(content) | |
def build_skeleton(name, directory): | |
# validate name | |
if not NAME_REGEX.match(name): | |
raise RuntimeError("Project name does not match regex convention: '{}'".format(NAME_REGEX.pattern)) | |
# sanity check | |
# does it match already known modules? | |
if name in sys.modules.keys(): | |
raise RuntimeError("Project name '{}' conflicts with an existing module in sys.modules.".format(name)) | |
# get package name | |
package_name = build_package_name(name) | |
# check directory | |
if directory is None: | |
directory = './' + name | |
path = os.path.abspath(os.path.expanduser(directory)) | |
print green_wrap("Creating {} project skeleton in {}...".format(name, path)) | |
if not os.path.exists(path): | |
print grey_wrap("Creating project directory..") | |
os.makedirs(path) | |
# generate items | |
module_path = os.path.join(path, package_name) | |
if not os.path.exists(module_path): | |
print grey_wrap("Creating module directory..") | |
os.makedirs(module_path) | |
print grey_wrap("Creating blank module..") | |
with open(os.path.join(module_path, '__init__.py'), 'w') as f: | |
f.write('') | |
print grey_wrap("Writing .gitignore..") | |
with open(os.path.join(path, '.gitignore'), 'w') as f: | |
f.write(GITIGNORE_CONTENT) | |
print grey_wrap("Writing setup.py..") | |
with open(os.path.join(path, 'setup.py'), 'w') as f: | |
f.write(SETUPPY_CONTENT.format(name=name, package_name=package_name)) | |
print grey_wrap("Writing setup.cfg..") | |
with open(os.path.join(path, 'setup.cfg'), 'w') as f: | |
f.write(SETUPCFG_CONTENT.format(name=name, package_name=package_name)) | |
print grey_wrap("Writing requirements files..") | |
with open(os.path.join(path, 'requirements.txt'), 'w') as f: | |
f.write("") | |
with open(os.path.join(path, 'test_requirements.txt'), 'w') as f: | |
f.write("pytest>=3.0.0\n") | |
f.write("pytest_cov>=2.4.0\n") | |
print grey_wrap("Writing README.md..") | |
with open(os.path.join(path, 'README.md'), 'w') as f: | |
f.write(README_CONTENT.format(name=name, package_name=package_name)) | |
tests_path = os.path.join(path, 'tests') | |
if not os.path.exists(tests_path): | |
print grey_wrap("Creating tests directory..") | |
os.makedirs(tests_path) | |
print grey_wrap("Writing blank test..") | |
with open(os.path.join(tests_path, 'test_{}.py'.format(package_name)), 'w') as f: | |
f.write(TEST_CONTENT.format(package_name=package_name)) | |
print green_wrap("Done.") | |
print "" | |
print blue_wrap("Readme:") | |
print blue_wrap("-" * 80) | |
print README_CONTENT.format(name=name, package_name=package_name).strip() | |
return path | |
def main(): | |
parser = argparse.ArgumentParser( | |
description='Generates a basic package skeleton including .gitignore, setup.py, etc..' | |
) | |
parser.add_argument('project_name', help="the name of the package (and module). Must follow naming conventions") | |
parser.add_argument('target_directory', nargs='?', default=None, help="the directory in which to create the files") | |
args = parser.parse_args() | |
build_skeleton(args.project_name, args.target_directory) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment