Skip to content

Commit

Permalink
Merge pull request #45 from toabctl/improv3
Browse files Browse the repository at this point in the history
Remove bitdeli image from README
  • Loading branch information
toabctl committed Jun 29, 2016
2 parents 5305c8d + a028993 commit f4ccef4
Show file tree
Hide file tree
Showing 8 changed files with 323 additions and 91 deletions.
10 changes: 0 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,6 @@ Py2pack: Generate distribution packages from PyPI
.. image:: https://travis-ci.org/saschpe/py2pack.png?branch=master
:target: https://travis-ci.org/saschpe/py2pack

.. image:: https://pypip.in/d/py2pack/badge.png
:target: https://pypi.python.org/pypi/py2pack

.. image:: https://pypip.in/v/py2pack/badge.png
:target: https://pypi.python.org/pypi/py2pack

This script allows to generate RPM spec or DEB dsc files from Python modules.
It allows to list Python modules or search for them on the Python Package Index
Expand Down Expand Up @@ -154,8 +149,3 @@ To run a single test class via `tox`_, use i.e.:
.. _`virtual environment`: http://www.virtualenv.org
.. _`tox`: http://testrun.org/tox


.. image:: https://d2weczhvl823v0.cloudfront.net/saschpe/py2pack/trend.png
:alt: Bitdeli badge
:target: https://bitdeli.com/free

48 changes: 25 additions & 23 deletions py2pack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@

import py2pack.proxy
import py2pack.requires
import py2pack.utils


TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'templates') # absolute template path
Expand Down Expand Up @@ -90,14 +91,30 @@ def fetch(args):
urllib.urlretrieve(url['url'], url['filename']) # download the object behind the URL


def _parse_setup_py(file, data):
d = py2pack.requires._requires_from_setup_py(file)
data.update(d)
def _parse_setup_py(filename, setup_filename, data):
"""parse the given setup_filename from the filename (usually a tarball)
this is not executing the setup.py file but parsing it with regex."""
names = []
if tarfile.is_tarfile(filename):
with tarfile.open(filename) as f:
names = f.getnames()
d = py2pack.requires._requires_from_setup_py(
f.extractfile(setup_filename))
data.update(d)
elif zipfile.is_zipfile(filename):
with zipfile.ZipFile(filename) as f:
names = f.namelist()
with f.open(setup_filename) as s:
d = py2pack.requires._requires_from_setup_py(s)
data.update(d)
return names


def _run_setup_py(tarfile, setup_filename, data):
d = py2pack.requires._requires_from_setup_py_run(tarfile, setup_filename)
def _run_setup_py(tarfile, data):
with py2pack.utils._extract_to_tempdir(tarfile) as (tmp_dir, names):
d = py2pack.requires._requires_from_setup_py_run(tmp_dir)
data.update(d)
return names


def _canonicalize_setup_data(data):
Expand Down Expand Up @@ -159,25 +176,10 @@ def _augment_data_from_tarball(args, filename, data):
docs_re = re.compile("{0}-{1}\/((?:AUTHOR|ChangeLog|CHANGES|COPYING|LICENSE|NEWS|README).*)".format(args.name, args.version), re.IGNORECASE)
shell_metachars_re = re.compile("[|&;()<>\s]")

if tarfile.is_tarfile(filename):
with tarfile.open(filename) as f:
names = f.getnames()
if args.run:
_run_setup_py(f, setup_filename, data)
else:
_parse_setup_py(f.extractfile(setup_filename), data)
_canonicalize_setup_data(data)
elif zipfile.is_zipfile(filename):
with zipfile.ZipFile(filename) as f:
names = f.namelist()
if args.run:
_run_setup_py(f, setup_filename, data)
else:
with f.open(setup_filename) as s:
_parse_setup_py(s, data)
_canonicalize_setup_data(data)
if args.run:
names = _run_setup_py(filename, data)
else:
return
names = _parse_setup_py(filename, setup_filename, data)

for name in names:
match = re.match(docs_re, name)
Expand Down
63 changes: 63 additions & 0 deletions py2pack/get_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016, Thomas Bechtold <tbechtold@suse.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.


# The file contains a distutils command to print metadata to the console
# or write it to a file.


from distutils.core import Command
import json


class get_metadata(Command):
"""a distutils command to get metadata"""
description = "get package metadata"
user_options = [
("output=", "o", "output for metadata json")
]

def initialize_options(self):
self.output = None

def finalize_options(self):
pass

def run(self):
data = {}
if self.distribution.has_ext_modules():
data["is_extension"] = True
if getattr(self.distribution, "scripts"):
data["scripts"] = self.distribution.scripts
if getattr(self.distribution, "test_suite"):
data["test_suite"] = self.distribution.test_suite
if getattr(self.distribution, "install_requires"):
data["install_requires"] = self.distribution.install_requires
if getattr(self.distribution, "extras_require"):
data["extras_require"] = self.distribution.extras_require
if getattr(self.distribution, "tests_require"):
data["tests_require"] = self.distribution.tests_require
if getattr(self.distribution, "data_files"):
data["data_files"] = self.distribution.data_files
if getattr(self.distribution, "entry_points"):
data["entry_points"] = self.distribution.entry_points

if self.output:
with open(self.output, "w+") as f:
f.write(json.dumps(data, indent=2))
else:
print(json.dumps(data, indent=2))
98 changes: 64 additions & 34 deletions py2pack/requires.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,67 +18,97 @@
from __future__ import absolute_import
from __future__ import print_function

import distutils.core
import json
import os
import re
import setuptools.sandbox
import shutil
import six
import subprocess
import sys
import tempfile
import textwrap


def _safe_eval(descr, code, fallback):
try:
return eval(code)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
code = re.sub("(?m)^", " ", code)
sys.stderr.write(
textwrap.dedent("""\
WARNING: Exception encountered:
%s: %s
whilst eval'ing code for '%s' parameter in setup.py:
%s
Try regenerating with the --run option.
""") % (exc_type.__name__, exc_value, descr, code))
return fallback


def _requires_from_setup_py(file):
"""read requirements from the setup.py file"""
data = {}
contents = six.u(file.read())
fixme = "FIXME: failed to extract %s from setup.py"
match = re.search("ext_modules", contents)
if match:
data["is_extension"] = True
match = re.search("[(,]\s*scripts\s*=\s*(\[.*?\])", contents, flags=re.DOTALL)
if match:
data["scripts"] = eval(match.group(1))
data["scripts"] = \
_safe_eval("scripts", match.group(1), [fixme % "scripts"])
match = re.search("test_suite\s*=\s*(.*)", contents)
if match:
data["test_suite"] = eval(match.group(1))
data["test_suite"] = \
_safe_eval("test_suite", match.group(1), [fixme % "test_suite"])
match = re.search("install_requires\s*=\s*(\[.*?\])", contents, flags=re.DOTALL)
if match:
data["install_requires"] = eval(match.group(1))
data["install_requires"] = \
_safe_eval("install_requires", match.group(1), [fixme % "install_requires"])
match = re.search("extras_require\s*=\s*(\{.*?\})", contents, flags=re.DOTALL)
if match:
data["extras_require"] = eval(match.group(1))
data["extras_require"] = \
_safe_eval("extras_require", match.group(1), [fixme % "extras_require"])
match = re.search("tests_require\s*=\s*(\{.*?\})", contents, flags=re.DOTALL)
if match:
data["tests_require"] = \
_safe_eval("tests_require", match.group(1), [fixme % "tests_require"])
match = re.search("data_files\s*=\s*(\[.*?\])", contents, flags=re.DOTALL)
if match:
data["data_files"] = eval(match.group(1))
data["data_files"] = \
_safe_eval("data_files", match.group(1), [fixme % "data_files"])
match = re.search('entry_points\s*=\s*(\{.*?\}|""".*?"""|".*?")', contents, flags=re.DOTALL)
if match:
data["entry_points"] = eval(match.group(1))
data["entry_points"] = \
_safe_eval("entry_points", match.group(1), [fixme % "entry_points"])
return data


def _requires_from_setup_py_run(tar_file, setup_filename):
"""run setup.py from a tarfile in a setuptools sandbox"""
tempdir = tempfile.mkdtemp()
setuptools.sandbox.DirectorySandbox(tempdir).run(lambda: tar_file.extractall(tempdir))

setup_filename = os.path.join(tempdir, setup_filename)
distutils.core._setup_stop_after = "config"
setuptools.sandbox.run_setup(setup_filename, "")
dist = distutils.core._setup_distribution
shutil.rmtree(tempdir)

def _requires_from_setup_py_run(tmp_dir):
"""run the get_metadata command via the setup.py in the given tmp_dir.
the output of get_metadata is json and is stored in a tempfile
which is then read in and returned as data"""
data = {}
if dist.ext_modules:
data["is_extension"] = True
if dist.scripts:
data["scripts"] = dist.scripts
if dist.test_suite:
data["test_suite"] = dist.test_suite
if dist.install_requires:
data["install_requires"] = dist.install_requires
if dist.extras_require:
data["extras_require"] = dist.extras_require
if dist.data_files:
data["data_files"] = dist.data_files
if dist.entry_points:
data["entry_points"] = dist.entry_points
try:
current_cwd = os.getcwd()
tempfile_json = tempfile.NamedTemporaryFile()
# if there is a single subdir, enter that one
dir_list = os.listdir(tmp_dir)
if len(dir_list) == 1 and os.path.isdir(dir_list[0]):
os.chdir(os.path.join(tmp_dir, dir_list[0]))
else:
os.chdir(tmp_dir)
# generate a temporary json file which contains the metadata
cmd = "python setup.py -q --command-packages py2pack " \
"get_metadata -o %s " % tempfile_json.name
subprocess.check_output(cmd, shell=True)
with open(tempfile_json.name, "r") as f:
data = json.loads(f.read())
finally:
os.chdir(current_cwd)
return data
29 changes: 5 additions & 24 deletions py2pack/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,32 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import glob
import os
import re
import shutil
import subprocess
from distutils.core import Command


class CleanupCommand(Command):
patterns = [".coverage", ".tox", ".venv", "build", "dist", "*.egg", "*.egg-info"]
description = "Clean up project directory"
user_options = []

def initialize_options(self):
pass

def finalize_options(self):
pass

def run(self):
for pattern in CleanupCommand.patterns:
for f in glob.glob(pattern):
if os.path.isdir(f):
shutil.rmtree(f, ignore_errors=True)
else:
os.remove(f)
from py2pack.get_metadata import get_metadata


class DocCommand(Command):
Expand Down Expand Up @@ -91,8 +70,10 @@ def run(self):
def get_cmdclass():
"""Dictionary of all distutils commands defined in this module.
"""
return {"cleanup": CleanupCommand,
"spdx_update": SPDXUpdateCommand}
return {
"spdx_update": SPDXUpdateCommand,
"get_metadata": get_metadata,
}


def parse_requirements(requirements_file='requirements.txt'):
Expand Down
48 changes: 48 additions & 0 deletions py2pack/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2016, Thomas Bechtold <tbechtold@suse.com>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from contextlib import contextmanager
import os
import shutil
import tarfile
import tempfile
import zipfile


@contextmanager
def _extract_to_tempdir(filename):
"""extract the given filename to a tempdir and change the cwd to the
new tempdir"""
tempdir = tempfile.mkdtemp(prefix="py2pack_")
current_cwd = os.getcwd()
names = []
try:
if tarfile.is_tarfile(filename):
with tarfile.open(filename) as f:
names = f.getnames()
f.extractall(tempdir)
elif zipfile.is_zipfile(filename):
with zipfile.ZipFile(filename) as f:
names = f.namelist()
f.extractall(tempdir)
else:
raise Exception("Can not extract '%s'. Not a tar or zip file" % filename)
os.chdir(tempdir)
yield tempdir, names
finally:
os.chdir(current_cwd)
shutil.rmtree(tempdir)

0 comments on commit f4ccef4

Please sign in to comment.