Skip to content

Commit

Permalink
Add bear upload tool
Browse files Browse the repository at this point in the history
  • Loading branch information
Adrianzatreanu committed Jul 20, 2016
1 parent 763aa4e commit f7836d1
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,4 @@ Gemfile.lock
.Rprofile
*.swp
venv
bears/upload/
141 changes: 141 additions & 0 deletions bears/generate_package.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import argparse
import glob
import os
import shutil
from string import Template
import subprocess
import sys

from bears import VERSION
from coalib.collecting.Importers import iimport_objects
from coalib.parsing.Globbing import glob


def touch(file_name):
"""
Creates an empty file. An existing file remains untouched.
:param file_name: Name of the file.
"""
open(file_name, 'a').close()


def create_file_from_template(template_file, output_file, substitution_dict):
"""
Creates a file from a template file, using a substitution dict.
:param template_file: The template file.
:param output_file: The file to be written.
:param substitution_dict: The dict from which the substitutions are taken.
"""
with open(template_file) as fl:
template = fl.read()
template = Template(template).safe_substitute(**substitution_dict)

with open(output_file, 'w') as output_handle:
output_handle.write(template)


def create_file_structure_for_packages(root_folder, file_to_copy, object_name):
"""
Creates a file structure for the packages to be uploaded. The structure
will be ``root_folder/object_name/object_name/file_to_copy``.
The file structure has two object_name folders because ``setup.py`` file
needs files to be one level deeper than itself so that they can be uploaded.
:param root_folder: The folder in which the packages are going to be
generated.
:param file_to_copy: The file that is going to be generated the package for.
:param object_name: The name of the object that is inside the file_to_copy.
"""
upload_package_folder = os.path.join(root_folder, object_name, object_name)
os.makedirs(upload_package_folder, exist_ok=True)
touch(os.path.join(upload_package_folder, '__init__.py'))
shutil.copyfile(file_to_copy, os.path.join(upload_package_folder,
object_name + '.py'))


def perform_register(path, file_name):
"""
Register the directory to PyPi, after creating a ``sdist`` and
a ``bdist_wheel``.
:param path: The file on which the register should be done.
"""
subprocess.call(
[sys.executable, 'setup.py', 'sdist', 'bdist_wheel'], cwd=path)
subprocess.call(['twine', 'register', '-r', 'pypitest', os.path.join(
path, 'dist', file_name + '.tar.gz')])
subprocess.call(['twine', 'register', '-r', 'pypitest', os.path.join(
path, 'dist', file_name + '-py3-none-any.whl')])


def perform_upload(path):
"""
Uploads the directory to PyPi.
:param path: The folder in which the upload should be done.
"""
subprocess.call(
['twine', 'upload', '-r', 'pypitest', path + '/dist/*'])


def create_upload_parser():
"""
Creates a parser for command line arguments.
:return: Parser arguments.
"""
parser = argparse.ArgumentParser(
description='Generates PyPi packages from bears.')
parser.add_argument('-r', '--register',
help='Register the packages on PyPi',
action='store_true')
parser.add_argument('-u', '--upload', help='Upload the packages on PyPi',
action='store_true')
return parser


def main():
args = create_upload_parser().parse_args()

os.makedirs(os.path.join('bears', 'upload'), exist_ok=True)

bear_file_name_list = glob('bears/**/*Bear.py')

for bear_file_name in bear_file_name_list:
bear_object = next(iimport_objects(
bear_file_name, attributes='kind', local=True),
None)
if bear_object:
bear_name, _ = os.path.splitext(os.path.basename(bear_file_name))
create_file_structure_for_packages(
os.path.join('bears', 'upload'), bear_file_name, bear_name)

substitution_dict = {'NAME': repr(bear_name),
'VERSION': repr(VERSION),
'AUTHORS': str(bear_object.AUTHORS),
'AUTHORS_EMAILS':
str(bear_object.AUTHORS_EMAILS),
'MAINTAINERS': str(bear_object.maintainers),
'MAINTAINERS_EMAILS':
str(bear_object.maintainers_emails),
'PLATFORMS': str(bear_object.PLATFORMS),
'LICENSE': str(bear_object.LICENSE),
'LONG_DESCRIPTION': str(bear_object.__doc__)}

create_file_from_template(os.path.join('bears', 'setup.py.in'),
os.path.join('bears', 'upload',
bear_name, 'setup.py'),
substitution_dict)

bear_dist_name = bear_name + '-' + VERSION
if args.register:
perform_register(os.path.join('bears', 'upload', bear_name),
bear_dist_name)
if args.upload:
perform_upload(os.path.join('bears', 'upload', bear_name))


if __name__ == '__main__': # pragma: no cover
sys.exit(main())
2 changes: 2 additions & 0 deletions bears/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
twine~=1.7.4
wheel~=8.1.2
44 changes: 44 additions & 0 deletions bears/setup.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import locale

from setuptools import setup


try:
locale.getlocale()
except (ValueError, UnicodeError):
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')


if __name__ == "__main__":
try:
setup(name=$NAME,
version=$VERSION,
description="$NAME bear for coala (http://coala.rtfd.org/)",
authors=$AUTHORS,
authors_emails=$AUTHORS_EMAILS,
maintainers=$MAINTAINERS,
maintainers_emails=$MAINTAINERS_EMAILS,
platforms=$PLATFORMS,
license='$LICENSE',
long_description="""$LONG_DESCRIPTION""",
classifiers=[
"Development Status :: 4 - Beta",
"Environment :: Console",
"Environment :: Win32 (MS Windows)",
"Intended Audience :: Science/Research",
"Intended Audience :: Developers",
"Programming Language :: "
"Python :: Implementation :: CPython",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3 :: Only",
"Topic :: Scientific/Engineering :: Information Analysis",
"Topic :: Software Development :: Quality Assurance",
"Topic :: Text Processing :: Linguistic"])

finally:
print('[WARN] If you do not install the bears using the coala'
'installation tool, there may be problems with the dependencies'
'and they may not work.')
129 changes: 129 additions & 0 deletions tests/generate_packageTest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import os
import shutil
import sys
import unittest
from unittest.mock import patch
from bears.generate_package import (touch, create_file_from_template,
create_file_structure_for_packages,
perform_register, perform_upload, main,
create_upload_parser)


class touchTest(unittest.TestCase):

def setUp(self):
if os.path.exists('TestFile.py'):
os.remove('TestFile.py')

def test_file_doesnt_exist(self):
self.assertFalse(os.path.exists('TestFile.py'))
touch('TestFile.py')
self.assertTrue(os.path.exists('TestFile.py'))

def tearDown(self):
os.remove('TestFile.py')


class create_file_from_templateTest(unittest.TestCase):

SUBST_FILE = os.path.join(
'tests', 'generate_package_input_files', 'substituted_file.py')
TEMPL_FILE = os.path.join(
'tests', 'generate_package_input_files', 'template_file.py.in')

def test_output_file(self):
data = {'who': 'George', 'sport': 'swimming'}
create_file_from_template(self.TEMPL_FILE, self.SUBST_FILE, data)
with open(self.SUBST_FILE) as fl:
substituted_file = fl.read()
self.assertEqual(substituted_file, 'George has gone swimming again.\n')

def tearDown(self):
os.remove(self.SUBST_FILE)


class create_file_structure_for_packagesTest(unittest.TestCase):

TEST_FILE_PATH = os.path.join('folder', 'Test', 'Test', 'Test.py')
INIT_FILE_PATH = os.path.join('folder', 'Test', 'Test', '__init__.py')

def test_structure(self):
touch('TestFile.py')
create_file_structure_for_packages('folder', 'TestFile.py', 'Test')
self.assertTrue(os.path.exists(self.TEST_FILE_PATH))
self.assertTrue(os.path.exists(self.INIT_FILE_PATH))

def tearDown(self):
shutil.rmtree('folder')


class create_upload_parserTest(unittest.TestCase):

def test_parser(self):
self.assertTrue(create_upload_parser().parse_args(['--upload']).upload)


class perform_registerTest(unittest.TestCase):

@patch('subprocess.call')
def test_command(self, call_mock):
perform_register('.', 'MarkdownBear-0.8.0.dev20160623094115')
call_mock.assert_called_with(
['twine', 'register', '-r', 'pypitest', os.path.join(
'.', 'dist',
'MarkdownBear-0.8.0.dev20160623094115-py3-none-any.whl')])


class perform_uploadTest(unittest.TestCase):

@patch('subprocess.call')
def test_command(self, call_mock):
perform_upload('.')
call_mock.assert_called_with(['twine', 'upload', '-r', 'pypitest',
'./dist/*'])


class mainTest(unittest.TestCase):

CSS_BEAR_SETUP_PATH = os.path.join(
'bears', 'upload', 'CSSLintBear', 'setup.py')
NO_BEAR_PATH = os.path.join('bears', 'BadBear', 'NoBearHere.py')

def setUp(self):
self.old_argv = sys.argv

def test_main(self):
sys.argv = ["generate_package.py"]
main()
self.assertTrue(os.path.exists(os.path.join('bears', 'upload')))
with open(self.CSS_BEAR_SETUP_PATH) as fl:
setup_py = fl.read()
self.assertIn("Check code for syntactical or semantical", setup_py)

@patch('bears.generate_package.perform_upload')
def test_upload(self, call_mock):
sys.argv = ["generate_package.py", "--upload"]
main()
for call_object in call_mock.call_args_list:
self.assertRegex(call_object[0][0], r".+Bear")

@patch('bears.generate_package.perform_register')
def test_register(self, call_mock):
sys.argv = ["generate_package.py", "--register"]
main()
for call_object in call_mock.call_args_list:
self.assertRegex(call_object[0][0], r".+Bear")

def test_no_bear_object(self):
if not os.path.exists(self.NO_BEAR_PATH):
os.makedirs(os.path.join('bears', 'BadBear'))
touch(self.NO_BEAR_PATH)
sys.argv = ["generate_package.py"]
main()
self.assertFalse(os.path.exists(os.path.join(
'bears', 'upload', 'BadBear')))
shutil.rmtree(os.path.join('bears', 'BadBear'))

def tearDown(self):
shutil.rmtree(os.path.join('bears', 'upload'))
sys.argv = self.old_argv
1 change: 1 addition & 0 deletions tests/generate_package_input_files/template_file.py.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
$who has gone $sport again.

0 comments on commit f7836d1

Please sign in to comment.