Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
srbdev committed Oct 19, 2015
1 parent 68b9bb8 commit 6f7a0ec
Show file tree
Hide file tree
Showing 23 changed files with 276 additions and 4 deletions.
12 changes: 12 additions & 0 deletions packages/slycat/cca.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import numpy
import scipy.linalg
import scipy.stats
import slycat.email

def cca(X, Y, scale_inputs=True, force_positive=None, significant_digits=None):
"""Compute Canonical Correlation Analysis (CCA).
Expand Down Expand Up @@ -42,25 +43,35 @@ def cca(X, Y, scale_inputs=True, force_positive=None, significant_digits=None):

# Validate our inputs ...
if not isinstance(X, numpy.ndarray) or not isinstance(Y, numpy.ndarray):
slycat.email.send_error("cca.py", "X and Y must be numpy.ndarray instances.")
raise TypeError("X and Y must be numpy.ndarray instances.")
if X.ndim != 2 or Y.ndim != 2:
slycat.email.send_error("cca.py", "X and Y must have two dimensions.")
raise ValueError("X and Y must have two dimensions.")
if X.shape[0] != Y.shape[0]:
slycat.email.send_error("cca.py", "X and Y must contain the same number of rows.")
raise ValueError("X and Y must contain the same number of rows.")
if X.shape[0] < X.shape[1] or X.shape[0] < Y.shape[1]:
slycat.email.send_error("cca.py", "Number of rows must be >= the number of column in X, and >= the number of columns in Y.")
raise ValueError("Number of rows must be >= the number of columns in X, and >= the number of columns in Y.")
if X.shape[1] < 1 or Y.shape[1] < 1:
slycat.email.send_error("cca.py", "X and Y must each contain at least one column.")
raise ValueError("X and Y must each contain at least one column.")
for column in numpy.column_stack((X, Y)).T:
if column.min() == column.max():
slycat.email.send_error("cca.py", "Columns in X and Y cannot be constant.")
raise ValueError("Columns in X and Y cannot be constant.")
if not isinstance(scale_inputs, bool):
slycat.email.send_error("cca.py", "scale_inputs must be a boolean.")
raise TypeError("scale_inputs must be a boolean.")
if not isinstance(force_positive, (type(None), numbers.Integral)):
slycat.email.send_error("cca.py", "force_positive must be an integer or None.")
raise TypeError("force_positive must be an integer or None.")
if force_positive is not None and (force_positive < 0 or force_positive >= Y.shape[1]):
slycat.email.send_error("cca.py", "force_positive must be in the range [0, number of Y columns).")
raise ValueError("force_positive must be in the range [0, number of Y columns).")
if not isinstance(significant_digits, (type(None), numbers.Integral)):
slycat.email.send_error("cca.py", "significant_digits must be an integer or None.")
raise TypeError("significant_digits must be an integer or None.")

eps = numpy.finfo("double").eps
Expand All @@ -86,6 +97,7 @@ def cca(X, Y, scale_inputs=True, force_positive=None, significant_digits=None):

# We validate this here, to avoid computing the rank twice.
if X.shape[0] < Xrank or X.shape[0] < Yrank:
slycat.email.send_error("cca.py", "Number of rows must be >= rank(X), and >= rank(Y).")
raise ValueError("Number of rows must be >= rank(X), and >= rank(Y).")

L, D, M = scipy.linalg.svd(numpy.dot(Q1.T, Q2), full_matrices=False)
Expand Down
11 changes: 11 additions & 0 deletions packages/slycat/darray.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"""

import numpy
import slycat.email

class Prototype(object):
"""Abstract interface for all darray implementations."""
Expand Down Expand Up @@ -72,15 +73,18 @@ class Stub(Prototype):
"""darray implementation that only stores array metadata (dimensions and attributes)."""
def __init__(self, dimensions, attributes):
if len(dimensions) < 1:
slycat.email.send_error("darray.py Stub.__init__", "At least one dimension is required.")
raise ValueError("At least one dimension is required.")
if len(attributes) < 1:
slycat.email.send_error("darray.py Stub.__init__", "At least one attribute is required.")
raise ValueError("At least one attribute is required.")

self._dimensions = [dict(name=_require_dimension_name(dimension["name"]), type=_require_dimension_type(dimension.get("type", "int64")), begin=_require_dimension_bound(dimension.get("begin", 0)), end=_require_dimension_bound(dimension["end"])) for dimension in dimensions]
self._attributes = [dict(name=_require_attribute_name(attribute["name"]), type=_require_attribute_type(attribute["type"])) for attribute in attributes]

for dimension in self._dimensions:
if dimension["begin"] != 0:
slycat.email.send_error("darray.py Stub.__init__", "Dimension range must being with 0.")
raise ValueError("Dimension range must begin with 0.")

@property
Expand Down Expand Up @@ -114,12 +118,14 @@ def __init__(self, dimensions, attributes, data):
Stub.__init__(self, dimensions, attributes)

if len(attributes) != len(data):
slycat.email.send_error("darray.py MemArray.__init__", "Attribute and data counts must match.")
raise ValueError("Attribute and data counts must match.")

self._data = [numpy.array(attribute) for attribute in data]

for attribute in self._data:
if attribute.shape != self.shape:
slycat.email.send_error("darray.py MemArray.__init__", "Attribute data must match array shape.")
raise ValueError("Attribute data must match array shape.")

def get_statistics(self, attribute=0):
Expand All @@ -145,28 +151,33 @@ def set_data(self, attribute, slice, data):

def _require_attribute_name(name):
if not isinstance(name, basestring):
slycat.email.send_error("darray.py _require_attribute_name", "Attribute name must be a string.")
raise ValueError("Attribute name must be a string.")
return name

def _require_attribute_type(type):
if type not in _require_attribute_type.allowed_types:
slycat.email.send_error("darray.py _require_attribute_type", "Attribute type must be one of %s" % ",".join(_require_attribute_type.allowed_types))
raise ValueError("Attribute type must be one of %s" % ",".join(_require_attribute_type.allowed_types))
return type
_require_attribute_type.allowed_types = set(["int8", "int16", "int32", "int64", "uint8", "uint16", "uint32", "uint64", "float32", "float64", "string", "bool"])

def _require_dimension_name(name):
if not isinstance(name, basestring):
slycat.email.send_error("darray.py _require_attribute_name", "Dimension name must be a string.")
raise ValueError("Dimension name must be a string.")
return name

def _require_dimension_type(type):
if type not in _require_dimension_type.allowed_types:
slycat.email.send_error("darray.py _require_dimension_type", "Dimension type must be one of %s" % ",",join(_require_dimension_type.allowed_types))
raise ValueError("Dimension type must be one of %s" % ",".join(_require_dimension_type.allowed_types))
return type
_require_dimension_type.allowed_types = set(["int64"])

def _require_dimension_bound(bound):
if not isinstance(bound, int):
slycat.email.send_error("darray.py _require_dimension_bound", "Dimension bound must be an integer.")
raise ValueError("Dimension bound must be an integer.")
return bound

9 changes: 8 additions & 1 deletion packages/slycat/hdf5.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import numpy
import os
import slycat.darray
import slycat.email

class DArray(slycat.darray.Prototype):
"""Slycat darray implementation that stores data in an HDF5 file."""
Expand Down Expand Up @@ -159,21 +160,24 @@ def set_data(self, attribute, hyperslice, data):
----------
attribute : integer
The zero-based integer index of the attribute to be overwritten.
hyperslice : integer, :class:`slice`, :class:`Ellipsis`, or tuple containing one or more integer, :class:`slice`, and :class:`Ellipsis` instances.
hyperslice : integer, :class:`slice`, :class:`Ellipsis`, or tuple containing one or more integer, :class:`slice`, and :class:`Ellipsis` instances.
Defines the attribute region to be overwritten.
data : numpy.ndarray
Data to be written to the attribute.
"""

if not (0 <= attribute and attribute < len(self.attributes)):
slycat.email.send_error("hdf5.py set_data", "Attribute index %s out-of-range." % attribute)
raise ValueError("Attribute index %s out-of-range." % attribute)
if isinstance(hyperslice, (numbers.Integral, slice, type(Ellipsis))):
pass
elif isinstance(hyperslice, tuple):
for i in hyperslice:
if not isinstance(i, (numbers.Integral, slice, type(Ellipsis))):
slycat.email.send_error("hdf5.py set_data", "Unsupported hyperslice type.")
raise ValueError("Unsupported hyperslice type.")
else:
slycat.email.send_error("hdf5.py set_data", "Unsupported hyperslice type.")
raise ValueError("Unsupported hyperslice type.")

# Store the data.
Expand Down Expand Up @@ -273,6 +277,7 @@ def store_array(self, array_index, array):
array : :class:`slycat.hdf5.DArray`
"""
if not isinstance(array, slycat.darray.Prototype):
slycat.email.send_error("hdf5.py store_array", "A slycat.darray is required.")
raise ValueError("A slycat.darray is required.")

index = tuple([slice(dimension["begin"], dimension["end"]) for dimension in array.dimensions])
Expand Down Expand Up @@ -302,6 +307,7 @@ def start_arrayset(file):
arrayset : :class:`slycat.hdf5.ArraySet`
"""
if not isinstance(file, h5py.File):
slycat.email.send_error("hdf5.py start_arrayset", "An open h5py.File is required.")
raise ValueError("An open h5py.File is required.")
file.create_group("array")
return ArraySet(file)
Expand All @@ -312,6 +318,7 @@ def start_arrayset(file):
def dtype(type):
"""Convert a string attribute type into a dtype suitable for use with h5py."""
if type not in dtype.type_map.keys():
slycat.email.send_error("hdf5.py dtype", "Unsupported type: {}".format(type))
raise Exception("Unsupported type: {}".format(type))
return dtype.type_map[type]
dtype.type_map = {"int8":"int8", "int16":"int16", "int32":"int32", "int64":"int64", "uint8":"uint8", "uint16":"uint16", "uint32":"uint32", "uint64":"uint64", "float32":"float32", "float64":"float64", "string":h5py.special_dtype(vlen=unicode), "float":"float32", "double":"float64"}
Expand Down
3 changes: 3 additions & 0 deletions packages/slycat/hyperchunks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numbers
import numpy
import slycat.hyperchunks.grammar
import slycat.email

def parse(string):
"""Parse a string hyperchunks representation.
Expand Down Expand Up @@ -93,6 +94,7 @@ def attributes(self, attribute_count):
for index in numpy.arange(start, stop, step):
yield Array(index, hyperchunk.attributes, hyperchunk.order, hyperchunk.hyperslices)
else:
slycat.email.send_error("hyperchunks.__init__.py", "Unexpected array: %r" % arrays)
raise ValueError("Unexpected array: %r" % arrays)

def tostring(value):
Expand Down Expand Up @@ -152,5 +154,6 @@ def tostring(value):
if isinstance(value, slice):
return ("%s:%s" % ("" if value.start is None else value.start, "" if value.stop is None else value.stop)) + ("" if value.step is None else ":%s" % value.step)

slycat.email.send_error("hyperchunks.__init__.py", "Unknown value: %s" % value)
raise ValueError("Unknown value: %s" % value)

10 changes: 10 additions & 0 deletions packages/slycat/web/client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import requests.exceptions as exceptions
import shlex
import slycat.darray
import slycat.email
import sys
import time

Expand All @@ -39,6 +40,7 @@ def _require_array_ranges(ranges):
elif isinstance(ranges, list):
return ranges
else:
slycat.email.send_error("slycat.web.client.__init__.py", "Not a valid ranges object.")
raise Exception("Not a valid ranges object.")

class ArgumentParser(argparse.ArgumentParser):
Expand Down Expand Up @@ -127,6 +129,7 @@ def request(self, method, path, **keywords):
return body
except:
log.debug(log_message)
slycat.email.send_error("slycat.web.client.__init__.py request", "%s" % log_message)
raise

###########################################################################################################3
Expand Down Expand Up @@ -653,13 +656,17 @@ def put_model_arrayset_data(self, mid, aid, hyperchunks, data, force_json=False)
"""
# Sanity check arguments
if not isinstance(mid, basestring):
slycat.email.send_error("slycat.web.client.__init__.py put_model_arrayset_data", "Model id must be a string")
raise ValueError("Model id must be a string.")
if not isinstance(aid, basestring):
slycat.email.send_error("slycat.web.client.__init__.py put_model_arrayset_data", "Artifact id must be a string")
raise ValueError("Artifact id must be a string.")
if not isinstance(hyperchunks, basestring):
slycat.email.send_error("slycat.web.client.__init__.py put_model_arrayset_data", "Hyperchunks specification must be a string.")
raise ValueError("Hyperchunks specification must be a string.")
for chunk in data:
if not isinstance(chunk, numpy.ndarray):
slycat.email.send_error("slycat.web.client.__init__.py put_model_arrayset_data", "Data chunk must be a numpy array.")
raise ValueError("Data chunk must be a numpy array.")

# Mark whether every data chunk is numeric ... if so, we can send the data in binary form.
Expand Down Expand Up @@ -752,10 +759,12 @@ def find_project(self, name):
projects = [project for project in self.get_projects()["projects"] if project["name"] == name]

if len(projects) > 1:
slycat.email.send_error("slycat.web.client.__init__.py find_project", "More than one project matched the given name.")
raise Exception("More than one project matched the given name.")
elif len(projects) == 1:
return projects[0]
else:
slycat.email.send_error("slycat.web.client.__init__.py find_project", "No project matched the given name.")
raise Exception("No project matched the given name.")

def find_or_create_project(self, name, description=""):
Expand Down Expand Up @@ -785,6 +794,7 @@ def find_or_create_project(self, name, description=""):
projects = [project for project in self.get_projects()["projects"] if project["name"] == name]

if len(projects) > 1:
slycat.email.send_error("slycat.web.client.__init__.py find_or_create_project", "More than one project matched the given name. Try using a different project name instead.")
raise Exception("More than one project matched the given name. Try using a different project name instead.")
elif len(projects) == 1:
return projects[0]["_id"]
Expand Down
13 changes: 13 additions & 0 deletions packages/slycat/web/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import numpy
import os
import shutil
import slycat.email
import slycat.hdf5
import slycat.hyperchunks
import slycat.web.server.hdf5
Expand Down Expand Up @@ -56,6 +57,7 @@ def evaluate(hdf5_array, expression, expression_type, expression_level = 0):
elif expression.operator == "not in":
left = numpy.in1d(left, right, invert=True)
else:
slycat.email.send_error("slycat.web.server.__init__.py evaluate", "Unknown operator: %s" % expression.operator)
raise ValueError("Unknown operator: %s" % expression.operator)
return left
elif isinstance(expression, slycat.hyperchunks.grammar.FunctionCall):
Expand All @@ -68,10 +70,12 @@ def evaluate(hdf5_array, expression, expression_type, expression_level = 0):
order = order[::-1]
return order
else:
slycat.email.send_error("slycat.web.server.__init__.py evaluate", "Unknown function: %s" % expression.name)
raise ValueError("Unknown function: %s" % expression.name)
elif isinstance(expression, slycat.hyperchunks.grammar.List):
return expression.values
else:
slycat.email.send_error("slycat.web.server.__init__.py evaluate", "Unknown expression: %s" % expression)
raise ValueError("Unknown expression: %s" % expression)

def update_model(database, model, **kwargs):
Expand Down Expand Up @@ -226,6 +230,7 @@ def get_model_arrayset_data(database, model, aid, hyperchunks):
def get_model_parameter(database, model, aid):
key = "artifact:%s" % aid
if key not in model:
slycat.email.send_error("slycat.web.server.__init__.py get_model_parameter", "Unknown artifact: %s" % aid)
raise KeyError("Unknown artifact: %s" % aid)
return model["artifact:" + aid]

Expand Down Expand Up @@ -283,6 +288,7 @@ def put_model_arrayset_data(database, model, aid, hyperchunks, data):
hdf5_array = hdf5_arrayset[array.index]
for attribute in array.attributes(len(hdf5_array.attributes)):
if not isinstance(attribute.expression, slycat.hyperchunks.grammar.AttributeIndex):
slycat.email.send_error("slycat.web.server.__init__.py put_model_arrayset_data", "Cannot write to computed attribute.")
raise ValueError("Cannot write to computed attribute.")
stored_type = slycat.hdf5.dtype(hdf5_array.attributes[attribute.expression.index]["type"])
for hyperslice in attribute.hyperslices():
Expand All @@ -306,9 +312,11 @@ def put_model_file(database, model, aid, value, content_type, input=False):
def get_model_file(database, model, aid):
artifact = model.get("artifact:%s" % aid, None)
if artifact is None:
slycat.email.send_error("slycat.web.server.__init__.py get_model_file", "cherrypy.HTTPError 404")
raise cherrypy.HTTPError(404)
artifact_type = model["artifact-types"][aid]
if artifact_type != "file":
slycat.email.send_error("slycat.web.server.__init__.py get_model_file", "cherrypy.HTTPError 404 %s is not a file artifact." % aid)
raise cherrypy.HTTPError("400 %s is not a file artifact." % aid)
fid = artifact
return database.get_attachment(model, fid)
Expand Down Expand Up @@ -338,6 +346,7 @@ def put_model_inputs(database, model, source, deep_copy=False):
database.put_attachment(model, original_content, filename=original_value, content_type=original_content_type)
model["artifact:%s" % aid] = original_value
else:
slycat.email.send_error("slycat.web.server.__init__.py put_model_inputs", "Cannot copy unknown input artifact type %s." % original_type)
raise Exception("Cannot copy unknown input artifact type %s." % original_type)
model["artifact-types"][aid] = original_type
model["input-artifacts"] = list(set(model["input-artifacts"] + [aid]))
Expand Down Expand Up @@ -394,6 +403,7 @@ def get_remote_file(sid, path):

def post_model_file(mid, input=None, sid=None, path=None, aid=None, parser=None, **kwargs):
if input is None:
slycat.email.send_error("slycat.web.server.__init__.py put_model_file", "Required input parameter is missing.")
raise Exception("Required input parameter is missing.")

if path is not None and sid is not None:
Expand All @@ -402,11 +412,13 @@ def post_model_file(mid, input=None, sid=None, path=None, aid=None, parser=None,
# TODO verify that the file exists first...
file = session.sftp.file(path).read()
else:
slycat.email.send_error("slycat.web.server.__init__.py post_model_file", "Must supply path and sid parameters.")
raise Exception("Must supply path and sid parameters.")

if parser is None:
Exception("Required parser parameter is missing.")
if parser not in slycat.web.server.plugin.manager.parsers:
slycat.email.send_error("slycat.web.server.__init__.py post_model_file", "Unknown parser plugin: %s." % parser)
raise Exception("Unknown parser plugin: %s." % parser)

database = slycat.web.server.database.couchdb.connect()
Expand All @@ -415,4 +427,5 @@ def post_model_file(mid, input=None, sid=None, path=None, aid=None, parser=None,
try:
slycat.web.server.plugin.manager.parsers[parser]["parse"](database, model, input, [file], [aid], **kwargs)
except Exception as e:
slycat.email.send_error("slycat.web.server.__init__.py post_model_file", "%s" % e)
raise Exception("%s" % e)
4 changes: 4 additions & 0 deletions packages/slycat/web/server/database/couchdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import threading
import time
import uuid
import slycat.email

class Database:
"""Wraps a :class:`couchdb.client.Database` to convert CouchDB exceptions into CherryPy exceptions."""
Expand All @@ -40,6 +41,7 @@ def save(self, *arguments, **keywords):
try:
return self._database.save(*arguments, **keywords)
except couchdb.http.ServerError as e:
slycat.email.send_error("slycat.web.server.database.couchdb.py save", "%s %s" % (e.message[0], e.message[1][1]))
raise cherrypy.HTTPError("%s %s" % (e.message[0], e.message[1][1]))

def view(self, *arguments, **keywords):
Expand All @@ -54,8 +56,10 @@ def get(self, type, id):
try:
document = self[id]
except couchdb.client.http.ResourceNotFound:
slycat.email.send_error("slycat.web.server.database.couchdb.py get", "cherrypy.HTTPError 404")
raise cherrypy.HTTPError(404)
if document["type"] != type:
slycat.email.send_error("slycat.web.server.database.couchdb.py get", "cherrypy.HTTPError 404")
raise cherrypy.HTTPError(404)
return document

Expand Down
Loading

0 comments on commit 6f7a0ec

Please sign in to comment.