Skip to content

Instantly share code, notes, and snippets.

@astromechza
Last active February 10, 2017 13:09
Show Gist options
  • Save astromechza/4a3fce3b89928db42ffa to your computer and use it in GitHub Desktop.
Save astromechza/4a3fce3b89928db42ffa to your computer and use it in GitHub Desktop.
pyskeleton.py: Generate a basic Python package, ready for development.
#!/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