diff --git a/.travis.yml b/.travis.yml
index e049b27..c531075 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,8 @@
language: python
-python:
- - "2.7"
+env:
+ - TOXENV=py27
+ - TOXENV=py33
install:
- - pip install .
+ - pip install tox
script:
- - nosetests
- - pyqi
+ - tox
diff --git a/ChangeLog.md b/ChangeLog.md
index 5a1a3d5..4561dce 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -4,6 +4,8 @@ pyqi ChangeLog
pyqi 0.3.1-dev
--------------
+* added an HDF5 implicit dataset extender
+* native python 3 support
* painless profiling: just set the environment variable PYQI_PROFILE_COMMAND
pyqi 0.3.1
diff --git a/doc/index.rst b/doc/index.rst
index 5a7bcea..de999ea 100644
--- a/doc/index.rst
+++ b/doc/index.rst
@@ -6,7 +6,7 @@ What is pyqi?
pyqi (canonically pronounced *pie chee*) is a Python framework designed to support wrapping general *commands* in multiple types of *interfaces*, including at the command line, HTML, and API levels.
-pyqi's only requirement is a working Python 2.7 installation.
+pyqi's only requirement is a working Python 2.7 or 3.3 installation.
Why should I care?
------------------
diff --git a/pyqi/commands/make_optparse.py b/pyqi/commands/make_optparse.py
index b99930a..6c26323 100644
--- a/pyqi/commands/make_optparse.py
+++ b/pyqi/commands/make_optparse.py
@@ -10,7 +10,7 @@
from __future__ import division
from operator import attrgetter
-from pyqi.core.command import (Command, CommandIn, CommandOut,
+from pyqi.core.command import (Command, CommandIn, CommandOut,
ParameterCollection)
from pyqi.commands.code_header_generator import CodeHeaderGenerator
@@ -81,7 +81,7 @@
# # value will be made available to Handler. This name
# # can be either an underscored or dashed version of the
# # option name (e.g., 'output_fp' or 'output-fp')
- # InputName='output-fp'),
+ # InputName='output-fp'),
#
# An example option that does not map to a CommandIn.
# OptparseResult(Parameter=cmd_out_lookup('some_other_result'),
@@ -112,7 +112,7 @@
class MakeOptparse(CodeHeaderGenerator):
BriefDescription = "Consume a Command, stub out an optparse configuration"
LongDescription = """Construct and stub out the basic optparse configuration for a given Command. This template provides comments and examples of what to fill in."""
-
+
CommandIns = ParameterCollection(
CodeHeaderGenerator.CommandIns.Parameters + [
CommandIn(Name='command', DataType=Command,
@@ -155,8 +155,9 @@ def run(self, **kwargs):
action = 'store'
data_type = cmdin.DataType
- fmt = {'name':cmdin.Name, 'datatype':data_type, 'action':action,
- 'required':str(cmdin.Required),
+ fmt = {'name':cmdin.Name,
+ 'datatype':getattr(data_type, '__name__', None),
+ 'action':action, 'required':str(cmdin.Required),
'help':cmdin.Description, 'default_block':default_block}
cmdin_formatted.append(input_format % fmt)
diff --git a/pyqi/core/command.py b/pyqi/core/command.py
index b497a15..cf0dc83 100644
--- a/pyqi/core/command.py
+++ b/pyqi/core/command.py
@@ -135,9 +135,9 @@ def __call__(self, **kwargs):
try:
result = self.run(**kwargs)
- except Exception:
+ except Exception as e:
self._logger.fatal('Error executing command: %s' % self_str)
- raise
+ raise e
else:
self._logger.info('Completed command: %s' % self_str)
diff --git a/pyqi/core/hdf5.py b/pyqi/core/hdf5.py
new file mode 100644
index 0000000..e233046
--- /dev/null
+++ b/pyqi/core/hdf5.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, The BiPy Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+from __future__ import division
+
+__credits__ = ["Daniel McDonald"]
+
+try:
+ import h5py
+except ImportError:
+ raise ImportError("h5py is required for functionality in this module")
+
+VLENSTR = h5py.special_dtype(vlen=str)
+
+class AutoExtendHDF5(object):
+ """Allow for implicitly extendable datasets"""
+ def __init__(self, f):
+ self.f = f
+ self._known_datasets = []
+
+ def __del__(self):
+ for n in self._known_datasets:
+ self._finalize(n)
+
+ def create_dataset(self, name, dtype):
+ """Create a tracked dataset that will automatically reshape"""
+ self.f.create_dataset(name, shape=(1,), maxshape=(None,),
+ chunks=True, dtype=dtype)
+ self.f[name].attrs['next_item'] = 0 # idx where next item can get written
+ self._known_datasets.append(name)
+
+ def extend(self, name, data, growth_factor=1):
+ """Extend an automatically growable dataset"""
+ n_data_items = len(data)
+ next_item = self.f[name].attrs['next_item']
+
+ # resize as needed
+ if (next_item + n_data_items) >= self.f[name].size:
+ new_size = next_item + n_data_items
+ new_size += int(new_size * growth_factor)
+ self.f[name].resize((new_size,))
+
+ # store the data
+ start = next_item
+ end = next_item + len(data)
+ self.f[name][start:end] = data
+ self.f[name].attrs['next_item'] = end
+
+ def _finalize(self, name):
+ """Resize a dataset to its correct size"""
+ actual_size = self.f[name].attrs.get('next_item', None)
+
+ if actual_size is None:
+ return
+
+ self.f[name].resize((actual_size,))
+ del self.f[name].attrs['next_item']
diff --git a/pyqi/core/interface.py b/pyqi/core/interface.py
index c2e1cd1..252ab1f 100644
--- a/pyqi/core/interface.py
+++ b/pyqi/core/interface.py
@@ -13,7 +13,6 @@
import importlib
from sys import exit, stderr
-from ConfigParser import SafeConfigParser
from glob import glob
from os.path import basename, dirname, expanduser, join
from pyqi.core.exception import IncompetentDeveloperError
@@ -193,13 +192,13 @@ class InterfaceInputOption(InterfaceOption):
"str": str,
"int": int,
"float": float,
- "long": long,
+ "long": int, # for python 3 compatibility as long is dropped
"complex": complex,
"tuple": tuple,
"dict": dict,
"list": list,
"set": set,
- "unicode": unicode,
+ "unicode": str, # for python 3 compatibility as all strings are unicode
"frozenset": frozenset
}
@@ -298,9 +297,9 @@ def get_command_config(command_config_module, cmd, exit_on_failure=True):
try:
cmd_cfg = importlib.import_module('.'.join([command_config_module,
python_cmd_name]))
- except ImportError, e:
+ except ImportError as e:
error_msg = str(e)
-
+
if exit_on_failure:
stderr.write("Unable to import the command configuration for "
"%s:\n" % cmd)
diff --git a/pyqi/core/interfaces/html/__init__.py b/pyqi/core/interfaces/html/__init__.py
index eff4675..87c05f0 100644
--- a/pyqi/core/interfaces/html/__init__.py
+++ b/pyqi/core/interfaces/html/__init__.py
@@ -14,7 +14,15 @@
import os
import types
import os.path
-from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+import sys
+
+from pyqi.util import get_version_string, is_py2
+
+if is_py2():
+ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+else:
+ from http.server import BaseHTTPRequestHandler, HTTPServer
+
from cgi import parse_header, parse_multipart, parse_qs, FieldStorage
from copy import copy
from glob import glob
@@ -24,7 +32,6 @@
from pyqi.core.factory import general_factory
from pyqi.core.exception import IncompetentDeveloperError
from pyqi.core.command import Parameter
-from pyqi.util import get_version_string
class HTMLResult(InterfaceOutputOption):
"""Base class for results for an HTML config file"""
@@ -36,7 +43,7 @@ def __init__(self, MIMEType=None, **kwargs):
class HTMLDownload(HTMLResult):
"""Result class for downloading a file from the server"""
- def __init__(self, FileExtension=None, FilenameLookup=None, DefaultFilename=None,
+ def __init__(self, FileExtension=None, FilenameLookup=None, DefaultFilename=None,
MIMEType='application/octet-stream', **kwargs):
super(HTMLDownload, self).__init__(MIMEType=MIMEType, **kwargs)
self.FileExtension = FileExtension
@@ -56,7 +63,6 @@ class HTMLInputOption(InterfaceInputOption):
bool: lambda x: x.value == "True",
int: lambda x: int(x.value),
float: lambda x: float(x.value),
- long: lambda x: long(x.value),
complex: lambda x: complex(x.value),
"upload_file": lambda x: x.file,
"multiple_choice": lambda x: x.value
@@ -76,17 +82,17 @@ def get_html(self, prefix, value=""):
"""Return the HTML needed for user input given a default value"""
if (not value) and (self.Default is not None):
value = self.Default
-
+
input_name = prefix + self.Name
string_input = lambda: '' % (input_name, value)
number_input = lambda: '' % (input_name, value)
- #html input files cannot have default values.
+ #html input files cannot have default values.
#If the html interface worked as a data service, this would be possible as submit would be ajax.
upload_input = lambda: '' % input_name
mchoice_input = lambda: ''.join(
- [ ('(%s)'
- % (choice, input_name, choice, 'checked="true"' if value == choice else ''))
+ [ ('(%s)'
+ % (choice, input_name, choice, 'checked="true"' if value == choice else ''))
for choice in self.Choices ]
)
@@ -96,7 +102,6 @@ def get_html(self, prefix, value=""):
bool: mchoice_input,
int: number_input,
float: number_input,
- long: number_input,
complex: string_input,
"multiple_choice": mchoice_input,
"upload_file": upload_input
@@ -110,7 +115,7 @@ def get_html(self, prefix, value=""):
self.Help,
'
| |
'
])
-
+
def _validate_option(self):
if self.Type not in self._type_handlers:
raise IncompetentDeveloperError("Unsupported Type in HTMLInputOption: %s" % self.Type)
@@ -130,7 +135,7 @@ def _validate_option(self):
class HTMLInterface(Interface):
"""An HTML interface"""
#Relative mapping wasn't working on a collegue's MacBook when pyqi was run outside of it's directory
- #Until I understand why that was the case and how to fix it, I am putting the style css here.
+ #Until I understand why that was the case and how to fix it, I am putting the style css here.
#This is not a permanent solution.
css_style = '\n'.join([
'html, body {',
@@ -189,7 +194,7 @@ def __init__(self, input_prefix="pyqi_", **kwargs):
self._html_input_prefix = input_prefix
self._html_interface_input = {}
super(HTMLInterface, self).__init__(**kwargs)
-
+
#Override
def __call__(self, in_, *args, **kwargs):
self._the_in_validator(in_)
@@ -199,14 +204,14 @@ def __call__(self, in_, *args, **kwargs):
'type': 'error',
'errors': errors
}
- else:
+ else:
cmd_result = self.CmdInstance(**cmd_input)
- self._the_out_validator(cmd_result)
+ self._the_out_validator(cmd_result)
return self._output_handler(cmd_result)
def _validate_inputs_outputs(self, inputs, outputs):
- super(HTMLInterface, self)._validate_inputs_outputs(inputs, outputs)
-
+ super(HTMLInterface, self)._validate_inputs_outputs(inputs, outputs)
+
if len(outputs) > 1:
raise IncompetentDeveloperError("There can be only one... output")
@@ -241,7 +246,7 @@ def _input_handler(self, in_, *args, **kwargs):
formatted_input = {}
for key in in_:
- mod_key = key[ len(self._html_input_prefix): ]
+ mod_key = key[ len(self._html_input_prefix): ]
formatted_input[mod_key] = in_[key]
if not formatted_input[mod_key].value:
formatted_input[mod_key] = None
@@ -319,11 +324,11 @@ def _output_handler(self, results):
if output.InputName is None:
handled_results = output.Handler(rk, results[rk])
else:
- handled_results = output.Handler(rk, results[rk],
+ handled_results = output.Handler(rk, results[rk],
self._html_interface_input[output.InputName])
else:
handled_results = results[rk]
-
+
if isinstance(output, HTMLDownload):
return self._output_download_handler(output, handled_results)
@@ -413,7 +418,7 @@ def route(self, path, output_writer):
self.send_header('Content-type', 'text/html')
self.end_headers()
output_writer(self.wfile.write)
-
+
self.wfile.close()
self._unrouted = False;
@@ -426,7 +431,7 @@ def command_route(self, command):
self.send_header('Content-type', 'text/html')
self.end_headers()
cmd_obj.command_page_writer(self.wfile.write, [], {})
-
+
self.wfile.close()
self._unrouted = False
@@ -441,14 +446,14 @@ def post_route(self, command, postvars):
'type':'error',
'errors':[e]
}
-
+
if result['type'] == 'error':
self.send_response(400)
self.send_header('Content-type', 'text/html')
self.end_headers()
cmd_obj.command_page_writer(self.wfile.write, result['errors'], postvars)
- elif result['type'] == 'page':
+ elif result['type'] == 'page':
self.send_response(200)
self.send_header('Content-type', result['mime_type'])
self.end_headers()
@@ -460,7 +465,7 @@ def post_route(self, command, postvars):
self.send_header('Content-disposition', 'attachment; filename='+result['filename'])
self.end_headers()
self.wfile.write(result['contents'])
-
+
self.wfile.close()
self._unrouted = False
@@ -508,8 +513,8 @@ def do_POST(self):
def start_server(port, module):
"""Start a server for the HTMLInterface on the specified port"""
interface_server = HTTPServer(("", port), get_http_handler(module))
- print "-- Starting server at http://localhost:%d --" % port
- print "To close the server, type 'ctrl-c' into this window."
+ print("-- Starting server at http://localhost:%d --" % port)
+ print("To close the server, type 'ctrl-c' into this window.")
try:
interface_server.serve_forever()
diff --git a/pyqi/core/interfaces/optparse/__init__.py b/pyqi/core/interfaces/optparse/__init__.py
index dd43c8c..85de1fc 100644
--- a/pyqi/core/interfaces/optparse/__init__.py
+++ b/pyqi/core/interfaces/optparse/__init__.py
@@ -13,7 +13,6 @@
"Jose Antonio Navas Molina"]
import os
-import types
from copy import copy
from glob import glob
from os.path import abspath, exists, isdir, isfile, split
@@ -436,7 +435,7 @@ def _check_multiple_choice(self):
if self.mchoices is None:
raise OptionError(
"must supply a list of mchoices for type '%s'" % self.type, self)
- elif type(self.mchoices) not in (types.TupleType, types.ListType):
+ elif type(self.mchoices) not in (tuple, list):
raise OptionError(
"choices must be a list of strings ('%s' supplied)"
% str(type(self.mchoices)).split("'")[1], self)
diff --git a/pyqi/core/interfaces/optparse/output_handler.py b/pyqi/core/interfaces/optparse/output_handler.py
index 94d7075..fe0fcb2 100644
--- a/pyqi/core/interfaces/optparse/output_handler.py
+++ b/pyqi/core/interfaces/optparse/output_handler.py
@@ -65,14 +65,14 @@ def print_list_of_strings(result_key, data, option_value=None):
``result_key`` and ``option_value`` are ignored.
"""
for line in data:
- print line
+ print(line)
def print_string(result_key, data, option_value=None):
"""Print the string
A newline will be printed before the data"""
- print ""
- print data
+ print("")
+ print(data)
def write_or_print_string(result_key, data, option_value=None):
"""Write a string to a file.
diff --git a/pyqi/util.py b/pyqi/util.py
index b82bad6..7408492 100644
--- a/pyqi/util.py
+++ b/pyqi/util.py
@@ -15,21 +15,29 @@
import importlib
from os import remove
from os.path import split, splitext
-import sys
-from subprocess import Popen, PIPE, STDOUT
+import sys
+from subprocess import Popen, PIPE
+
from pyqi.core.log import StdErrLogger
from pyqi.core.exception import MissingVersionInfoError
+def is_py2():
+ """Check if we're using Python 2"""
+ if sys.version_info.major == 2:
+ return True
+ else:
+ return False
+
def pyqi_system_call(cmd, shell=True, dry_run=False):
"""Call cmd and return (stdout, stderr, return_value).
- cmd: can be either a string containing the command to be run, or a
+ cmd: can be either a string containing the command to be run, or a
sequence of strings that are the tokens of the command.
- shell: value passed directly to Popen (default: True). See Python's
+ shell: value passed directly to Popen (default: True). See Python's
subprocess.Popen for a description of the shell parameter and how cmd
is interpreted differently based on its value.
dry_run: if True, print cmd and return ("", "", 0) (default: False)
-
+
This function is ported from QIIME (http://www.qiime.org), previously
named qiime_system_call. QIIME is a GPL project, but we obtained permission
from the authors of this function to port it to pyqi (and keep it under
@@ -48,7 +56,7 @@ def pyqi_system_call(cmd, shell=True, dry_run=False):
universal_newlines=True,
stdout=PIPE,
stderr=PIPE)
- # communicate pulls all stdout/stderr from the PIPEs to
+ # communicate pulls all stdout/stderr from the PIPEs to
# avoid blocking -- don't remove this line!
stdout, stderr = proc.communicate()
return_value = proc.returncode
@@ -69,8 +77,8 @@ def remove_files(list_of_filepaths, error_on_missing=True):
missing.append(fp)
if error_on_missing and missing:
- raise OSError, "Some filepaths were not accessible: %s" % '\t'.join(
- missing)
+ raise OSError("Some filepaths were not accessible: %s" % '\t'.join(
+ missing))
def old_to_new_command(driver_name, project_title, local_argv):
"""Deprecate an old-style script.
diff --git a/scripts/pyqi b/scripts/pyqi
index 2f3ebf3..16b5393 100755
--- a/scripts/pyqi
+++ b/scripts/pyqi
@@ -26,7 +26,6 @@ from pyqi.core.interface import get_command_names, get_command_config
from pyqi.core.interfaces.optparse import optparse_main, optparse_factory
from pyqi.util import get_version_string
from os.path import basename
-from string import ljust
from os import environ
### we actually have some flexibility here to make the driver interface agnostic as well
@@ -54,26 +53,25 @@ def usage(cmd_cfg_mod, command_names):
desc_limit = TERM_WIDTH - (INDENT + max_cmd + INDENT)
cmd_end = INDENT + max_cmd + INDENT
- print "usage: %s []" % argv[0]
- print
- print "The currently available commands are:"
+ print("usage: %s []\n" % argv[0])
+ print("The currently available commands are:")
# format:
# indent command indent description
for c, desc in valid_cmds:
- cmd_formatted = ljust(''.join([' ' * INDENT, c]), cmd_end)
- print ''.join([cmd_formatted, desc[:desc_limit]])
+ s = ''.join([' ' * INDENT, c])
+ cmd_formatted = s.ljust(cmd_end)
+ print(''.join([cmd_formatted, desc[:desc_limit]]))
if invalid_cmds:
- print
- print "The following commands could not be loaded:"
+ print("\nThe following commands could not be loaded:")
for c, error_msg in invalid_cmds:
- cmd_formatted = ljust(''.join([' ' * INDENT, c]), cmd_end)
- print ''.join([cmd_formatted, 'Error: %s' % error_msg])
+ s = ''.join([' ' * INDENT, c])
+ cmd_formatted = s.ljust(cmd_end)
+ print(''.join([cmd_formatted, 'Error: %s' % error_msg]))
- print
- print "See '%s help ' for more information on a specific command." % argv[0]
+ print("\nSee '%s help ' for more information on a specific command." % argv[0])
exit(0)
def get_cmd_obj(cmd_cfg_mod, cmd):
diff --git a/setup.py b/setup.py
index 0667db3..07769fd 100644
--- a/setup.py
+++ b/setup.py
@@ -21,14 +21,6 @@
from glob import glob
import sys
-# from https://wiki.python.org/moin/PortingPythonToPy3k
-try:
- # python 3.x
- from distutils.command.build_py import build_py_2to3 as build_py
-except ImportError:
- # python 2.x
- from distutils.command.build_py import build_py
-
# classes/classifiers code adapted from Celery:
# https://github.com/celery/celery/blob/master/setup.py
#
@@ -41,6 +33,7 @@
Topic :: Software Development :: User Interfaces
Programming Language :: Python
Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3.3
Programming Language :: Python :: Implementation :: CPython
Operating System :: OS Independent
Operating System :: POSIX
@@ -50,14 +43,13 @@
# Verify Python version
ver = '.'.join(map(str, [sys.version_info.major, sys.version_info.minor]))
-if ver not in ['2.7']:
- sys.stderr.write("Only Python >=2.7 and <3.0 is supported.")
+if ver not in ['2.7', '3.3']:
+ sys.stderr.write("Only Python 2.7 and 3.3 are supported.")
sys.exit(1)
long_description = """pyqi (canonically pronounced pie chee) is a Python framework designed to support wrapping general commands in multiple types of interfaces, including at the command line, HTML, and API levels."""
setup(name='pyqi',
- cmdclass={'build_py':build_py},
version=__version__,
license=__license__,
description='pyqi: expose your interface',
diff --git a/tests/test_commands/test_code_header_generator.py b/tests/test_commands/test_code_header_generator.py
index b1c91e7..dc1cc38 100644
--- a/tests/test_commands/test_code_header_generator.py
+++ b/tests/test_commands/test_code_header_generator.py
@@ -24,7 +24,7 @@ def test_run(self):
obs = self.cmd(author='bob', email='bob@bob.bob',
license='very permissive license',
copyright='what\'s that?', version='1.0')
- self.assertEqual(obs.keys(), ['result'])
+ self.assertEqual(list(obs.keys()), ['result'])
obs = obs['result']
self.assertEqual('\n'.join(obs), exp_header1)
@@ -34,14 +34,14 @@ def test_run(self):
license='very permissive license',
copyright='what\'s that?', version='1.0',
credits=['another person', 'another another person'])
- self.assertEqual(obs.keys(), ['result'])
+ self.assertEqual(list(obs.keys()), ['result'])
obs = obs['result']
self.assertEqual('\n'.join(obs), exp_header2)
# With no arguments
obs = self.cmd()
- self.assertEqual(obs.keys(), ['result'])
+ self.assertEqual(list(obs.keys()), ['result'])
obs = obs['result']
self.assertEqual('\n'.join(obs), exp_header3)
diff --git a/tests/test_commands/test_make_bash_completion.py b/tests/test_commands/test_make_bash_completion.py
index aad1c08..2305da2 100644
--- a/tests/test_commands/test_make_bash_completion.py
+++ b/tests/test_commands/test_make_bash_completion.py
@@ -75,7 +75,7 @@ def test_run(self):
params = {'command_config_module':self.temp_module_name,
'driver_name':'pyqi'}
obs = self.cmd(**params)
- self.assertEqual(obs.keys(), ['result'])
+ self.assertEqual(list(obs.keys()), ['result'])
self.assertEqual(obs['result'], outputandstuff)
diff --git a/tests/test_commands/test_make_command.py b/tests/test_commands/test_make_command.py
index 94a680c..25f083a 100644
--- a/tests/test_commands/test_make_command.py
+++ b/tests/test_commands/test_make_command.py
@@ -24,7 +24,7 @@ def test_run_command_code_generation(self):
obs = self.cmd(name='Test', author='bob', email='bob@bob.bob',
license='very permissive license',
copyright='what\'s that?', version='1.0')
- self.assertEqual(obs.keys(), ['result'])
+ self.assertEqual(list(obs.keys()), ['result'])
obs = obs['result']
self.assertEqual(obs, exp_command_code1.splitlines())
@@ -35,7 +35,7 @@ def test_run_test_code_generation(self):
license='very permissive license',
copyright='what\'s that?', version='1.0',
credits=['another person'], test_code=True)
- self.assertEqual(obs.keys(), ['result'])
+ self.assertEqual(list(obs.keys()), ['result'])
obs = obs['result']
self.assertEqual('\n'.join(obs), exp_test_code1)
diff --git a/tests/test_commands/test_make_optparse.py b/tests/test_commands/test_make_optparse.py
index d36dc2f..ae30658 100644
--- a/tests/test_commands/test_make_optparse.py
+++ b/tests/test_commands/test_make_optparse.py
@@ -22,7 +22,7 @@ def setUp(self):
def test_run(self):
exp = win_text
-
+
pc = CommandIn(Name='DUN', Required=True, DataType=str, Description="")
bool_param = CommandIn(Name='imabool', DataType=bool,
Description='zero or one', Required=False)
@@ -39,9 +39,9 @@ class stubby:
'copyright': 'what\'s that?',
'version': '1.0'
})
-
+
self.assertEqual(obs['result'], exp.splitlines())
-
+
win_text = """#!/usr/bin/env python
from __future__ import division
@@ -105,7 +105,7 @@ class stubby:
# Help='output filepath')
OptparseOption(Parameter=cmd_in_lookup('DUN'),
- Type=,
+ Type=str,
Action='store', # default is 'store', change if desired
Handler=None, # must be defined if desired
ShortName=None, # must be defined if desired
@@ -136,7 +136,7 @@ class stubby:
# # value will be made available to Handler. This name
# # can be either an underscored or dashed version of the
# # option name (e.g., 'output_fp' or 'output-fp')
- # InputName='output-fp'),
+ # InputName='output-fp'),
#
# An example option that does not map to a CommandIn.
# OptparseResult(Parameter=cmd_out_lookup('some_other_result'),
diff --git a/tests/test_core/test_hdf5.py b/tests/test_core/test_hdf5.py
new file mode 100644
index 0000000..fe3f737
--- /dev/null
+++ b/tests/test_core/test_hdf5.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013, The BiPy Development Team.
+#
+# Distributed under the terms of the Modified BSD License.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+from __future__ import division
+
+__credits__ = ["Daniel McDonald"]
+
+import unittest
+
+from unittest import TestCase, main
+from os import remove
+from numpy import array, hstack
+from pyqi.core.hdf5 import AutoExtendHDF5
+
+try:
+ import h5py
+ h5py_missing = False
+except ImportError:
+ h5py_missing = True
+
+@unittest.skipIf(h5py_missing, "h5py is not present")
+class HDF5AutoExtendTests(TestCase):
+ def setUp(self):
+ self.hdf5_file = h5py.File('_test_file.hdf5','w')
+ self.obj = AutoExtendHDF5(self.hdf5_file)
+
+ def tearDown(self):
+ remove('_test_file.hdf5')
+
+ def test_create_dataset(self):
+ """Create something"""
+ self.obj.create_dataset('test_group/test_ds', int)
+ self.assertEqual(array([0],int), self.obj.f['test_group/test_ds'][:])
+ self.assertEqual(self.obj._known_datasets, ['test_group/test_ds'])
+
+ def test_extend(self):
+ """Check the implicit resizing"""
+ name = 'test_group/test_ds'
+ fetch = lambda x: x[name][:x[name].attrs['next_item']]
+ size = lambda x: x[name].size
+
+ self.obj.create_dataset(name, int)
+ ds1 = array([1,2,3,4])
+ ds2 = array([5,10,20,50])
+ ds3 = array([44,44,44,123,123])
+ ds4 = array([1])
+
+ self.obj.extend(name, ds1)
+ self.assertTrue((fetch(self.obj.f) == ds1).all())
+ self.assertEqual(self.obj.f[name].attrs['next_item'], 4)
+ self.assertEqual(size(self.obj.f), 8)
+
+ self.obj.extend(name, ds2)
+ exp = hstack([ds1, ds2])
+ obs = fetch(self.obj.f)
+ self.assertTrue((obs == exp).all())
+ self.assertEqual(self.obj.f[name].attrs['next_item'], 8)
+ self.assertEqual(size(self.obj.f), 16)
+
+ self.obj.extend(name, ds3)
+ exp = hstack([ds1, ds2, ds3])
+ obs = fetch(self.obj.f)
+ self.assertTrue((obs == exp).all())
+ self.assertEqual(self.obj.f[name].attrs['next_item'], 13)
+ self.assertEqual(size(self.obj.f), 16)
+
+ self.obj.extend(name, ds4)
+ exp = hstack([ds1, ds2, ds3, ds4])
+ obs = fetch(self.obj.f)
+ self.assertTrue((obs == exp).all())
+ self.assertEqual(self.obj.f[name].attrs['next_item'], 14)
+ self.assertEqual(size(self.obj.f), 16)
+
+ def test_finalize(self):
+ """Make sure we can finalize a dataset"""
+ name = 'test_group/test_ds'
+ fetch = lambda x: x[name][:]
+ size = lambda x: x[name].size
+ self.obj.create_dataset(name, int)
+ self.obj.extend(name, array([1,2,3]))
+ self.obj.extend(name, array([1,2,3]))
+ self.obj.extend(name, array([1,2,3]))
+ self.obj.extend(name, array([1,2,3]))
+ self.obj.extend(name, array([1,2,3]))
+
+ self.assertEqual(size(self.obj.f), 24)
+ exp = array([1,2,3,1,2,3,1,2,3,1,2,3,1,2,3,0,0,0,0,0,0,0,0,0])
+ obs = fetch(self.obj.f)
+ self.assertTrue((obs == exp).all())
+
+ self.obj._finalize(name)
+ exp = array([1,2,3] * 5)
+ obs = fetch(self.obj.f)
+ self.assertTrue((obs == exp).all())
+
+ self.assertFalse('next_item' in self.obj.f[name].attrs)
+
+if __name__ == '__main__':
+ main()
diff --git a/tests/test_core/test_interface.py b/tests/test_core/test_interface.py
index 63e05c0..b0b292e 100644
--- a/tests/test_core/test_interface.py
+++ b/tests/test_core/test_interface.py
@@ -12,14 +12,17 @@
__credits__ = ["Greg Caporaso", "Daniel McDonald", "Doug Wendel",
"Jai Ram Rideout"]
+import sys
+
from unittest import TestCase, main
from pyqi.core.interface import get_command_names, get_command_config
+from pyqi.util import is_py2
import pyqi.interfaces.optparse.config.make_bash_completion
class TopLevelTests(TestCase):
def test_get_command_names(self):
"""Test that command names are returned from a config directory."""
- exp = ['make-bash-completion', 'make-command', 'make-optparse',
+ exp = ['make-bash-completion', 'make-command', 'make-optparse',
'make-release', 'serve-html-interface']
obs = get_command_names('pyqi.interfaces.optparse.config')
self.assertEqual(obs, exp)
@@ -40,8 +43,13 @@ def test_get_command_config(self):
'hopefully.nonexistent.python.module', 'umm',
exit_on_failure=False)
self.assertEqual(cmd_cfg, None)
- self.assertEqual(error_msg, 'No module named hopefully.nonexistent.'
- 'python.module.umm')
+
+ py2_err = 'No module named hopefully.nonexistent.python.module.umm'
+ py3_err = "No module named 'hopefully'"
+ if is_py2():
+ self.assertEqual(error_msg, py2_err)
+ else:
+ self.assertEqual(error_msg, py3_err)
if __name__ == '__main__':
diff --git a/tests/test_core/test_interfaces/test_html/test_input_handler.py b/tests/test_core/test_interfaces/test_html/test_input_handler.py
index e22dafe..7891672 100644
--- a/tests/test_core/test_interfaces/test_html/test_input_handler.py
+++ b/tests/test_core/test_interfaces/test_html/test_input_handler.py
@@ -10,8 +10,14 @@
__credits__ = ["Evan Bolyen"]
-from StringIO import StringIO
+import sys
from unittest import TestCase, main
+
+if sys.version_info.major == 2:
+ from StringIO import StringIO
+else:
+ from io import StringIO
+
from pyqi.core.exception import IncompetentDeveloperError
from pyqi.core.interfaces.html.input_handler import (load_file_lines,
load_file_contents)
@@ -34,8 +40,8 @@ def test_load_file_lines(self):
# can't load a string, etc...
self.assertRaises(IncompetentDeveloperError, load_file_lines, 'This is not a file')
result = load_file_lines(self.file_like_object)
- self.assertEqual(result,
- ["This is line 1",
+ self.assertEqual(result,
+ ["This is line 1",
"This is line 2",
"This is line 3"])
diff --git a/tests/test_core/test_interfaces/test_optparse/test_init.py b/tests/test_core/test_interfaces/test_optparse/test_init.py
index 9e85823..4ecffe0 100644
--- a/tests/test_core/test_interfaces/test_optparse/test_init.py
+++ b/tests/test_core/test_interfaces/test_optparse/test_init.py
@@ -100,7 +100,7 @@ def test_validate_inputs(self):
def test_input_handler(self):
obs = self.interface._input_handler(['--c','foo'])
- self.assertEqual(obs.items(), [('c', 'foo')])
+ self.assertEqual(list(obs.items()), [('c', 'foo')])
def test_build_usage_lines(self):
obs = self.interface._build_usage_lines([])
@@ -177,8 +177,10 @@ def setUp(self):
self._dirs_to_clean_up = []
def tearDown(self):
- map(remove, self._paths_to_clean_up)
- map(rmdir, self._dirs_to_clean_up)
+ for p in self._paths_to_clean_up:
+ remove(p)
+ for d in self._dirs_to_clean_up:
+ rmdir(d)
def test_check_existing_filepath(self):
# Check that returns the correct value when the file exists
@@ -199,8 +201,8 @@ def test_check_existing_filepath(self):
def test_check_existing_filepaths(self):
# Check that returns a list with the paths, in the same order as
# the input comma separated list
- tmp_f1, tmp_path1 = mkstemp(prefix='pyqi_tmp_')
- tmp_f2, tmp_path2 = mkstemp(prefix='pyqi_tmp_')
+ tmp_f1, tmp_path1 = mkstemp(prefix='pyqi_tmp_testf')
+ tmp_f2, tmp_path2 = mkstemp(prefix='pyqi_tmp_testf')
self._paths_to_clean_up = [tmp_path1, tmp_path2]
option = PyqiOption('-f', '--files_test', type='existing_filepaths')
exp = [tmp_path1, tmp_path2]
@@ -245,8 +247,8 @@ def test_check_existing_dirpath(self):
def test_check_existing_dirpaths(self):
# Check that returns a list with the paths, in the same order as the
# input comma separated list
- tmp_dirpath1 = mkdtemp(prefix='pyqi_tmp_')
- tmp_dirpath2 = mkdtemp(prefix='pyqi_tmp_')
+ tmp_dirpath1 = mkdtemp(prefix='pyqi_tmp_testd_')
+ tmp_dirpath2 = mkdtemp(prefix='pyqi_tmp_testd_')
self._dirs_to_clean_up = [tmp_dirpath1, tmp_dirpath2]
option = PyqiOption('-d', '--dirs_test', type='existing_dirpaths')
exp = [tmp_dirpath1, tmp_dirpath2]
diff --git a/tests/test_core/test_interfaces/test_optparse/test_output_handler.py b/tests/test_core/test_interfaces/test_optparse/test_output_handler.py
index c4c9a82..22a687c 100644
--- a/tests/test_core/test_interfaces/test_optparse/test_output_handler.py
+++ b/tests/test_core/test_interfaces/test_optparse/test_output_handler.py
@@ -13,7 +13,14 @@
import os
import sys
-from StringIO import StringIO
+
+from pyqi.util import is_py2
+
+if is_py2():
+ from StringIO import StringIO
+else:
+ from io import StringIO
+
from shutil import rmtree
from tempfile import mkdtemp
from unittest import TestCase, main
diff --git a/tests/test_util.py b/tests/test_util.py
index 30462b5..f751196 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -17,6 +17,7 @@
from pyqi.util import get_version_string
from pyqi.core.exception import MissingVersionInfoError
+
class UtilTests(TestCase):
def test_get_version_string(self):
"""Test extracting a version string given a module string."""
diff --git a/tox.ini b/tox.ini
index b1547c6..1b99b7a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,7 @@
[tox]
-envlist = py27
+envlist = py27,py33
[testenv]
-deps=nose
-commands=nosetests
+deps=nose
+commands=
+ nosetests
+ pyqi