diff --git a/LICENSE.txt b/LICENSE.txt index 6769ff16..ca9b93cc 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,22 +1,7 @@ -oct2py v0.3 -============= -Copyright (C) 2012 by Steven Silvester +Copyright (c) 2017, oct2py developers -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/oct2py/__init__.py b/oct2py/__init__.py index 437ab21e..66effbb8 100644 --- a/oct2py/__init__.py +++ b/oct2py/__init__.py @@ -1,6 +1,10 @@ # -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. + """ -Oct2Py is a means to seamlessly call M-files and GNU Octave functions from Python. +Oct2Py is a means to seamlessly call M-files and GNU Octave functions from +Python. It manages the Octave session for you, sharing data behind the scenes using MAT files. Usage is as simple as: @@ -17,32 +21,18 @@ If you want to run legacy m-files, do not have MATLAB(TM), and do not fully trust a code translator, this is your library. """ +from __future__ import absolute_import, print_function, division -__title__ = 'oct2py' -__version__ = '3.9.2' -__author__ = 'Steven Silvester' -__license__ = 'MIT' -__copyright__ = 'Copyright 2014-2016 Steven Silvester' -__all__ = ['Oct2Py', 'Oct2PyError', 'octave', 'Struct', 'demo', 'speed_check', - 'thread_check', '__version__', 'get_log'] - - -import imp -import functools -import os -import ctypes - -try: - import thread -except ImportError: - import _thread as thread - -from .core import Oct2Py, Oct2PyError -from .utils import Struct, get_log +from .core import Oct2Py +from .io import Struct +from .utils import get_log, Oct2PyError from .demo import demo from .speed_check import speed_check from .thread_check import thread_check +__version__ = '4.0-dev' +__all__ = ['Oct2Py', 'Oct2PyError', 'octave', 'Struct', 'demo', 'speed_check', + 'thread_check', '__version__', 'get_log'] try: octave = Oct2Py() @@ -63,7 +53,3 @@ def kill_octave(): os.system('killall -9 octave') os.system('killall -9 octave-cli') octave.restart() - - -# clean up namespace -del functools, imp, os, ctypes, thread diff --git a/oct2py/compat.py b/oct2py/compat.py index a5cb595b..a9e8fa77 100644 --- a/oct2py/compat.py +++ b/oct2py/compat.py @@ -1,4 +1,7 @@ # -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. + import sys import ctypes import os diff --git a/oct2py/core.py b/oct2py/core.py index 58020c4f..e8676987 100644 --- a/oct2py/core.py +++ b/oct2py/core.py @@ -1,11 +1,7 @@ -""" -.. module:: core - :synopsis: Main module for oct2py package. - Contains the core session object Oct2Py +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. -.. moduleauthor:: Steven Silvester - -""" from __future__ import print_function, absolute_import, division import logging @@ -17,8 +13,8 @@ from metakernel.pexpect import EOF, TIMEOUT from octave_kernel.kernel import OctaveEngine, STDIN_PROMPT -from .matwrite import write_file -from .utils import get_nout, Oct2PyError, get_log, read_file +from .io import read_file, write_file +from .utils import get_nout, Oct2PyError, get_log from .compat import unicode, input, string_types from .dynamic import ( _make_function_ptr_instance, _make_variable_ptr_instance, diff --git a/oct2py/demo.py b/oct2py/demo.py index cdafe143..e0921279 100644 --- a/oct2py/demo.py +++ b/oct2py/demo.py @@ -1,11 +1,8 @@ -""" -.. module:: demo - :synopsis: Play a demo script showing most of the p2oct api features. +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. -.. moduleauthor:: Steven Silvester - -""" -from __future__ import print_function +from __future__ import print_function, absolute_import import time from .compat import PY2 diff --git a/oct2py/dynamic.py b/oct2py/dynamic.py index f39026b3..3c967bea 100644 --- a/oct2py/dynamic.py +++ b/oct2py/dynamic.py @@ -1,3 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. + +from __future__ import absolute_import, print_function, division + import types import warnings import weakref @@ -5,8 +11,8 @@ import numpy as np from scipy.io.matlab.mio5 import MatlabObject -from oct2py.compat import PY2 -from oct2py.utils import get_nout +from .compat import PY2 +from .utils import get_nout class OctavePtr(object): diff --git a/oct2py/io.py b/oct2py/io.py new file mode 100644 index 00000000..d362c57e --- /dev/null +++ b/oct2py/io.py @@ -0,0 +1,424 @@ +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. + +from __future__ import absolute_import, print_function, division + +import inspect +import dis + +import numpy as np +from scipy.io import loadmat, savemat +from scipy.sparse import spmatrix + +from .compat import PY2 +from .dynamic import OctaveVariablePtr, OctaveUserClass +from .utils import Oct2PyError + + +def read_file(path, session=None): + """Read the data from the given file path. + """ + try: + data = loadmat(path, struct_as_record=True) + except UnicodeDecodeError as e: + raise Oct2PyError(str(e)) + out = dict() + for (key, value) in data.items(): + out[key] = _extract_data(value, session) + return out + + +def write_file(obj, path, oned_as='row', convert_to_float=True): + """Save a Python object to an Octave file on the given path. + """ + data = _encode(obj, convert_to_float) + try: + savemat(path, data, appendmat=False, oned_as=oned_as, + long_field_names=True) + except KeyError: # pragma: no cover + raise Exception('could not save mat file') + + +class Struct(dict): + """ + Octave style struct, enhanced. + + Notes + ===== + Supports dictionary and attribute style access. Can be pickled, + and supports code completion in a REPL. + + Examples + ======== + >>> from pprint import pprint + >>> from oct2py import Struct + >>> a = Struct() + >>> a.b = 'spam' # a["b"] == 'spam' + >>> a.c["d"] = 'eggs' # a.c.d == 'eggs' + >>> pprint(a) + {'b': 'spam', 'c': {'d': 'eggs'}} + """ + @classmethod + def from_value(cls, value, session=None): + """Create a struct from an Octave value and optional session. + """ + instance = Struct() + for name in value.dtype.names: + data = value[name] + if isinstance(data, np.ndarray) and data.dtype.kind == 'O': + data = value[name].squeeze().tolist() + instance[name] = _extract_data(data, session) + return instance + + def __getattr__(self, attr): + # Access the dictionary keys for unknown attributes. + try: + return self[attr] + except KeyError: + msg = "'Struct' object has no attribute %s" % attr + raise AttributeError(msg) + + def __getitem__(self, attr): + # Get a dict value; create a Struct if requesting a Struct member. + # Do not create a key if the attribute starts with an underscore. + if attr in self.keys() or attr.startswith('_'): + return dict.__getitem__(self, attr) + frame = inspect.currentframe() + # step into the function that called us + if frame.f_back.f_back and self._is_allowed(frame.f_back.f_back): + dict.__setitem__(self, attr, Struct()) + elif self._is_allowed(frame.f_back): + dict.__setitem__(self, attr, Struct()) + return dict.__getitem__(self, attr) + + def _is_allowed(self, frame): + # Check for allowed op code in the calling frame. + allowed = [dis.opmap['STORE_ATTR'], dis.opmap['LOAD_CONST'], + dis.opmap.get('STOP_CODE', 0)] + bytecode = frame.f_code.co_code + instruction = bytecode[frame.f_lasti + 3] + instruction = ord(instruction) if PY2 else instruction + return instruction in allowed + + __setattr__ = dict.__setitem__ + __delattr__ = dict.__delitem__ + + @property + def __dict__(self): + # Allow for code completion in a REPL. + return self.copy() + + +class StructArray(object): + """A Python representation of an Octave structure array. + + Notes + ===== + Supports value access by index and accessing fields by name or value. + Differs slightly from numpy indexing in that a single number index + is used to find the nth element in the array, mimicking + the behavior of a struct array in Octave. + + This class is not meant to be directly created by the user. It is + created automatically for structure array values received from Octave. + + Examples + ======== + >>> from oct2py import octave + >>> # generate the struct array + >>> octave.eval('foo = struct("bar", {1, 2}, "baz", {3, 4});') + >>> foo = octave.pull('foo') + >>> foo.bar # value access + [1.0, 2.0] + >>> foo['baz'] # item access + [3.0, 4.0] + >>> ell = foo[0] # index access + >>> foo[1]['baz'] + 4.0 + """ + @classmethod + def from_value(cls, value, session=None): + """Create a struct array from an Octave value and optional seesion.""" + instance = StructArray() + for i in range(value.size): + index = np.unravel_index(i, value.shape) + for name in value.dtype.names: + value[index][name] = _extract_data(value[index][name], session) + instance._value = value + return instance + + @classmethod + def to_value(cls, instance): + value = instance._value + out = np.empty(value.shape, value.dtype) + names = instance.fieldnames + for i in range(value.size): + index = np.unravel_index(i, value.shape) + item = out[index] + for name in names: + item[name] = _encode[item[name]] + return out + + @property + def fieldnames(self): + """The field names of the struct array.""" + return self._value.dtype.names + + def __setattr__(self, attr, value): + if attr not in ['_value', '_session'] and attr in self.fieldnames: + self._value[attr] = value + else: + object.__setattr__(self, attr, value) + + def __getattr__(self, attr): + # Access the dictionary keys for unknown attributes. + try: + return self[attr] + except KeyError: + msg = "'StructArray' object has no attribute %s" % attr + raise AttributeError(msg) + + def __getitem__(self, attr): + # Get an item from the struct array. + value = self._value + + # Get the values as a nested list. + if attr in self.fieldnames: + return _extract_data(value[attr]) + + # Support simple indexing. + if isinstance(attr, int): + if attr >= value.size: + raise IndexError('Index out of range') + index = np.unravel_index(attr, value.shape) + return StructElement(self._value[index]) + + # Otherwise use numpy indexing. + data = value[attr] + # Return a single value as a struct. + if data.size == 1: + return StructElement(data) + instance = StructArray() + instance._value = data + return instance + + def __repr__(self): + shape = self._value.shape + if len(shape) == 1: + shape = (shape, 1) + msg = 'x'.join(str(i) for i in shape) + msg += ' struct array containing the fields:' + for key in self.fieldnames: + msg += '\n %s' % key + return msg + + @property + def __dict__(self): + # Allow for code completion in a REPL. + data = dict() + for key in self.fieldnames: + data[key] = None + return data + + +class StructElement(object): + """An element of a structure array. + + Notes + ----- + Supports value access by index and accessing fields by name or value. + Does not support adding or removing fields. + + This class is not meant to be directly created by the user. It is + created automatically for structure array values received from Octave. + + Examples + -------- + >>> from oct2py import octave + >>> # generate the struct array + >>> octave.eval('foo = struct("bar", {1, 2}, "baz", {3, 4});') + >>> foo = octave.pull('foo') + >>> el = foo[0] + >>> el['baz'] + 3.0 + >>> el.bar = 'spam' + >>> el.bar + 'spam' + """ + + def __init__(self, data): + """Create a new struct element""" + self._data = data + + @classmethod + def to_value(cls, instance): + out = Struct() + for key in instance.fieldnames: + out[key] = _encode(instance[key]) + return out + + @property + def fieldnames(self): + """The field names of the struct array element.""" + return self._data.dtype.names + + def __getattr__(self, attr): + # Access the dictionary keys for unknown attributes. + if not attr.startswith('_') and attr in self.fieldnames: + return self.__getitem__(attr) + return object.__getattr__(self, attr) + + def __setattr__(self, attr, value): + if not attr.startswith('_') and attr in self.fieldnames: + self._data[attr] = value + return + object.__setattr__(self, attr, value) + + def __getitem__(self, item): + if item in self.fieldnames: + return self._data[item] + raise IndexError('Invalid index') + + def __setitem__(self, item, value): + self._data[item] = value + + def __repr__(self): + msg = 'struct array element containing the fields:' + for key in self.fieldnames: + msg += '\n %s' % key + return msg + + def __dict__(self): + """Allow for code completion in a REPL""" + data = dict() + for key in self.fieldnames: + data[key] = None + return data + + +def _extract_data(data, session=None): + # Extract each item of a list. + if isinstance(data, list): + return [_extract_data(v, session) for v in data] + + # Ignore leaf objects. + if not isinstance(data, np.ndarray): + return data + + # Convert user defined classes. + if hasattr(data, 'classname') and session: + cls = session._get_user_class(data.classname) + data = cls.from_value(data) + + # Extract struct data. + elif data.dtype.names: + # Singular struct + if data.size == 1: + data = Struct.from_value(data, session) + # Struct array + else: + data = StructArray.from_value(data, session) + + # Extract cells. + elif data.dtype.kind == 'O': + data = data.squeeze().tolist() + if not isinstance(data, list): + data = [data] + data = _extract_data(data, session) + + # Compress singleton values. + elif data.size == 1: + data = data.item() + + # Compress empty values. + elif data.size == 0: + if data.dtype.kind in 'US': + data = '' + else: + data = [] + + # Return parsed value. + return data + + +def _encode(data, convert_to_float): + """Convert the Python values to values suitable to sent to Octave. + """ + + # Handle variable pointer. + if isinstance(data, (OctaveVariablePtr)): + data = data.value + + # Handle a user defined object. + elif isinstance(data, OctaveUserClass): + data = OctaveUserClass.to_value(data) + + # Handle struct array. + elif isinstance(data, StructArray): + data = StructArray.to_value(data) + + # Handle struct element. + elif isinstance(data, StructElement): + data = StructElement.to_value(data) + + # Extract the values from dict and Struct objects. + if isinstance(data, dict): + out = dict() + for (key, value) in data.items(): + out[key] = _encode(value, convert_to_float) + return out + + # Send None as nan. + if data is None: + return np.NaN + + # Handle list-like types. + if isinstance(data, (tuple, set, list)): + is_tuple = isinstance(data, tuple) + data = [_encode(o, convert_to_float) for o in data] + + if not is_tuple: + # Convert to a numeric array if possible. + try: + return _handle_list(data, convert_to_float) + except ValueError: + pass + + # Create a cell object. + cell = np.empty((len(data),), dtype=object) + for i in range(len(data)): + cell[i] = data[i] + return cell + + # Sparse data must be floating type. + if isinstance(data, spmatrix): + return data.astype(np.float64) + + # Return other data types unchanged. + if not isinstance(data, np.ndarray): + return data + + # Complex 128 is the highest supported by savemat. + if data.dtype.name == 'complex256': + return data.astype(np.complex128) + + # Convert to float if applicable. + if convert_to_float and data.dtype.kind in 'ui': + return data.astype(np.float64) + + return data + + +def _handle_list(data, convert_to_float): + """Handle an encoded list.""" + + # Convert to an array. + data = np.array(data) + + # Only handle numeric types. + if data.dtype.kind not in 'uicf': + raise ValueError + + # Handle any other ndarray considerations. + return _encode(data, convert_to_float) diff --git a/oct2py/matwrite.py b/oct2py/matwrite.py deleted file mode 100644 index b803c786..00000000 --- a/oct2py/matwrite.py +++ /dev/null @@ -1,109 +0,0 @@ -""" -.. module:: matwrite - :synopsis: Write Python values into an MAT file for Octave. - Strives to preserve both value and type in transit. - -.. moduleauthor:: Steven Silvester - -""" -from __future__ import absolute_import, print_function, division - -from scipy.io import savemat -from scipy.sparse import spmatrix -import numpy as np - -from .dynamic import OctaveVariablePtr, OctaveUserClass -from .utils import StructArray, StructElement - - -def write_file(obj, path, oned_as='row', convert_to_float=True): - """Save a Python object to an Octave file on the given path. - """ - data = _encode(obj, convert_to_float) - try: - savemat(path, data, appendmat=False, oned_as=oned_as, - long_field_names=True) - except KeyError: # pragma: no cover - raise Exception('could not save mat file') - - -def _encode(data, convert_to_float): - """Convert the Python values to values suitable to sent to Octave. - """ - - # Handle variable pointer. - if isinstance(data, (OctaveVariablePtr)): - data = data.value - - # Handle a user defined object. - elif isinstance(data, OctaveUserClass): - data = OctaveUserClass.to_value(data) - - # Handle struct array. - elif isinstance(data, StructArray): - data = StructArray.to_value(data) - - # Handle struct element. - elif isinstance(data, StructElement): - data = StructElement.to_value(data) - - # Extract the values from dict and Struct objects. - if isinstance(data, dict): - out = dict() - for (key, value) in data.items(): - out[key] = _encode(value, convert_to_float) - return out - - # Send None as nan. - if data is None: - return np.NaN - - # Handle list-like types. - if isinstance(data, (tuple, set, list)): - is_tuple = isinstance(data, tuple) - data = [_encode(o, convert_to_float) for o in data] - - if not is_tuple: - # Convert to a numeric array if possible. - try: - return _handle_list(data, convert_to_float) - except ValueError: - pass - - # Create a cell object. - cell = np.empty((len(data),), dtype=object) - for i in range(len(data)): - cell[i] = data[i] - return cell - - # Sparse data must be floating type. - if isinstance(data, spmatrix): - return data.astype(np.float64) - - # Return other data types unchanged. - if not isinstance(data, np.ndarray): - return data - - # Complex 128 is the highest supported by savemat. - if data.dtype.name == 'complex256': - return data.astype(np.complex128) - - # Convert to float if applicable. - if convert_to_float and data.dtype.kind in 'ui': - return data.astype(np.float64) - - return data - - -def _handle_list(data, convert_to_float): - """Handle an encoded list.""" - - # Convert to an array. - data = np.array(data) - - # Only handle numeric types. - if data.dtype.kind not in 'uicf': - raise ValueError - - # Handle any other ndarray considerations. - return _encode(data, convert_to_float) diff --git a/oct2py/speed_check.py b/oct2py/speed_check.py index f957cb28..8fa253f4 100644 --- a/oct2py/speed_check.py +++ b/oct2py/speed_check.py @@ -1,16 +1,12 @@ -""" -.. module:: speed_test - :synopsis: Checks the speed penalty of the HDF transfers. +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. -.. moduleauthor:: Steven Silvester - - -""" -from __future__ import print_function +from __future__ import print_function, absolute_import import time import timeit import numpy as np -from oct2py import Oct2Py +from . import Oct2Py class SpeedCheck(object): diff --git a/oct2py/tests/test_conversions.py b/oct2py/tests/test_conversions.py index 42d14ef7..2746a24a 100644 --- a/oct2py/tests/test_conversions.py +++ b/oct2py/tests/test_conversions.py @@ -3,8 +3,7 @@ import numpy as np -from oct2py import Oct2Py -from oct2py.utils import Struct +from oct2py import Oct2Py, Struct from oct2py.compat import unicode, long diff --git a/oct2py/tests/test_misc.py b/oct2py/tests/test_misc.py index 9c374b81..96778cab 100644 --- a/oct2py/tests/test_misc.py +++ b/oct2py/tests/test_misc.py @@ -3,7 +3,6 @@ import logging import os import shutil -import sys import tempfile import pytest diff --git a/oct2py/tests/test_numpy.py b/oct2py/tests/test_numpy.py index 9dcde2a3..2b755d27 100644 --- a/oct2py/tests/test_numpy.py +++ b/oct2py/tests/test_numpy.py @@ -3,7 +3,6 @@ import numpy as np - from oct2py import Oct2Py diff --git a/oct2py/tests/test_roundtrip.py b/oct2py/tests/test_roundtrip.py index 4a996182..8bd2fef4 100644 --- a/oct2py/tests/test_roundtrip.py +++ b/oct2py/tests/test_roundtrip.py @@ -3,8 +3,7 @@ import numpy as np -from oct2py import Oct2Py, Oct2PyError -from oct2py.utils import Struct +from oct2py import Oct2Py, Oct2PyError, Struct from oct2py.compat import unicode, long diff --git a/oct2py/tests/test_usage.py b/oct2py/tests/test_usage.py index 2dcfe2c6..d45b5c7c 100644 --- a/oct2py/tests/test_usage.py +++ b/oct2py/tests/test_usage.py @@ -8,8 +8,7 @@ import numpy as np import pytest -from oct2py import Oct2Py, Oct2PyError -from oct2py.utils import Struct +from oct2py import Oct2Py, Oct2PyError, Struct from oct2py.compat import StringIO diff --git a/oct2py/thread_check.py b/oct2py/thread_check.py index fa4cd38d..2f84d6a6 100644 --- a/oct2py/thread_check.py +++ b/oct2py/thread_check.py @@ -1,15 +1,11 @@ -""" -.. module:: thread_test - :synopsis: Test Starting Multiple Threads. - Verify that they each have their own session +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. -.. moduleauthor:: Steven Silvester - -""" -from __future__ import print_function +from __future__ import print_function, absolute_import import threading import datetime -from oct2py import Oct2Py, Oct2PyError +from . import Oct2Py, Oct2PyError class ThreadClass(threading.Thread): diff --git a/oct2py/utils.py b/oct2py/utils.py index 0f1a2c64..ef29c6d0 100644 --- a/oct2py/utils.py +++ b/oct2py/utils.py @@ -1,15 +1,22 @@ -""" -.. module:: utils - :synopsis: Miscellaneous helper constructs +# -*- coding: utf-8 -*- +# Copyright (c) oct2py developers. +# Distributed under the terms of the MIT License. -.. moduleauthor:: Steven Silvester +from __future__ import absolute_import, print_function, division -""" import inspect import dis +import logging import sys -from oct2py.compat import PY2 + +from .compat import PY2 + + +class Oct2PyError(Exception): + """ Called when we can't open Octave or Octave throws an error + """ + pass def get_nout(): @@ -46,316 +53,6 @@ def get_nout(): return 1 -class Oct2PyError(Exception): - """ Called when we can't open Octave or Octave throws an error - """ - pass - - -class Struct(dict): - """ - Octave style struct, enhanced. - - Notes - ===== - Supports dictionary and attribute style access. Can be pickled, - and supports code completion in a REPL. - - Examples - ======== - >>> from pprint import pprint - >>> from oct2py import Struct - >>> a = Struct() - >>> a.b = 'spam' # a["b"] == 'spam' - >>> a.c["d"] = 'eggs' # a.c.d == 'eggs' - >>> pprint(a) - {'b': 'spam', 'c': {'d': 'eggs'}} - """ - @classmethod - def from_value(cls, value, session=None): - """Create a struct from an Octave value and optional session. - """ - instance = Struct() - for name in value.dtype.names: - data = value[name] - if isinstance(data, np.ndarray) and data.dtype.kind == 'O': - data = value[name].squeeze().tolist() - instance[name] = _extract_data(data, session) - return instance - - def __getattr__(self, attr): - # Access the dictionary keys for unknown attributes. - try: - return self[attr] - except KeyError: - msg = "'Struct' object has no attribute %s" % attr - raise AttributeError(msg) - - def __getitem__(self, attr): - # Get a dict value; create a Struct if requesting a Struct member. - # Do not create a key if the attribute starts with an underscore. - if attr in self.keys() or attr.startswith('_'): - return dict.__getitem__(self, attr) - frame = inspect.currentframe() - # step into the function that called us - if frame.f_back.f_back and self._is_allowed(frame.f_back.f_back): - dict.__setitem__(self, attr, Struct()) - elif self._is_allowed(frame.f_back): - dict.__setitem__(self, attr, Struct()) - return dict.__getitem__(self, attr) - - def _is_allowed(self, frame): - # Check for allowed op code in the calling frame. - allowed = [dis.opmap['STORE_ATTR'], dis.opmap['LOAD_CONST'], - dis.opmap.get('STOP_CODE', 0)] - bytecode = frame.f_code.co_code - instruction = bytecode[frame.f_lasti + 3] - instruction = ord(instruction) if PY2 else instruction - return instruction in allowed - - __setattr__ = dict.__setitem__ - __delattr__ = dict.__delitem__ - - @property - def __dict__(self): - # Allow for code completion in a REPL. - return self.copy() - - -class StructArray(object): - """A Python representation of an Octave structure array. - - Notes - ===== - Supports value access by index and accessing fields by name or value. - Differs slightly from numpy indexing in that a single number index - is used to find the nth element in the array, mimicking - the behavior of a struct array in Octave. - - This class is not meant to be directly created by the user. It is - created automatically for structure array values received from Octave. - - Examples - ======== - >>> from oct2py import octave - >>> # generate the struct array - >>> octave.eval('foo = struct("bar", {1, 2}, "baz", {3, 4});') - >>> foo = octave.pull('foo') - >>> foo.bar # value access - [1.0, 2.0] - >>> foo['baz'] # item access - [3.0, 4.0] - >>> ell = foo[0] # index access - >>> foo[1]['baz'] - 4.0 - """ - @classmethod - def from_value(cls, value, session=None): - """Create a struct array from an Octave value and optional seesion.""" - instance = StructArray() - for i in range(value.size): - index = np.unravel_index(i, value.shape) - for name in value.dtype.names: - value[index][name] = _extract_data(value[index][name], session) - instance._value = value - return instance - - @classmethod - def to_value(cls, instance): - return instance._value - - @property - def fieldnames(self): - """The field names of the struct array.""" - return self._value.dtype.names - - def __setattr__(self, attr, value): - if attr not in ['_value', '_session'] and attr in self.fieldnames: - self._value[attr] = value - else: - object.__setattr__(self, attr, value) - - def __getattr__(self, attr): - # Access the dictionary keys for unknown attributes. - try: - return self[attr] - except KeyError: - msg = "'StructArray' object has no attribute %s" % attr - raise AttributeError(msg) - - def __getitem__(self, attr): - # Get an item from the struct array. - value = self._value - - # Get the values as a nested list. - if attr in self.fieldnames: - return _extract_data(value[attr]) - - # Support simple indexing. - if isinstance(attr, int): - if attr >= value.size: - raise IndexError('Index out of range') - index = np.unravel_index(attr, value.shape) - return StructElement(self._value[index]) - - # Otherwise use numpy indexing. - data = value[attr] - # Return a single value as a struct. - if data.size == 1: - return StructElement(data) - instance = StructArray() - instance._value = data - return instance - - def __repr__(self): - shape = self._value.shape - if len(shape) == 1: - shape = (shape, 1) - msg = 'x'.join(str(i) for i in shape) - msg += ' struct array containing the fields:' - for key in self.fieldnames: - msg += '\n %s' % key - return msg - - @property - def __dict__(self): - # Allow for code completion in a REPL. - data = dict() - for key in self.fieldnames: - data[key] = None - return data - - -class StructElement(object): - """An element of a structure array. - - Notes - ----- - Supports value access by index and accessing fields by name or value. - Does not support adding or removing fields. - - This class is not meant to be directly created by the user. It is - created automatically for structure array values received from Octave. - - Examples - -------- - >>> from oct2py import octave - >>> # generate the struct array - >>> octave.eval('foo = struct("bar", {1, 2}, "baz", {3, 4});') - >>> foo = octave.pull('foo') - >>> el = foo[0] - >>> el['baz'] - 3.0 - >>> el.bar = 'spam' - >>> el.bar - 'spam' - """ - - def __init__(self, data): - """Create a new struct element""" - self._data = data - - @classmethod - def to_value(cls, instance): - return instance._data - - @property - def fieldnames(self): - """The field names of the struct array element.""" - return self._data.dtype.names - - def __getattr__(self, attr): - # Access the dictionary keys for unknown attributes. - if not attr.startswith('_') and attr in self.fieldnames: - return self.__getitem__(attr) - return object.__getattr__(self, attr) - - def __setattr__(self, attr, value): - if not attr.startswith('_') and attr in self.fieldnames: - self._data[attr] = value - return - object.__setattr__(self, attr, value) - - def __getitem__(self, item): - if item in self.fieldnames: - return self._data[item] - raise IndexError('Invalid index') - - def __setitem__(self, item, value): - self._data[item] = value - - def __repr__(self): - msg = 'struct array element containing the fields:' - for key in self.fieldnames: - msg += '\n %s' % key - return msg - - def __dict__(self): - """Allow for code completion in a REPL""" - data = dict() - for key in self.fieldnames: - data[key] = None - return data - - -def read_file(path, session=None): - """Read the data from the given file path. - """ - try: - data = loadmat(path, struct_as_record=True) - except UnicodeDecodeError as e: - raise Oct2PyError(str(e)) - out = dict() - for (key, value) in data.items(): - out[key] = _extract_data(value, session) - return out - - -def _extract_data(data, session=None): - # Extract each item of a list. - if isinstance(data, list): - return [_extract_data(v, session) for v in data] - - # Ignore leaf objects. - if not isinstance(data, np.ndarray): - return data - - # Convert user defined classes. - if hasattr(data, 'classname') and session: - cls = session._get_user_class(data.classname) - data = cls.from_value(data) - - # Extract struct data. - elif data.dtype.names: - # Singular struct - if data.size == 1: - data = Struct.from_value(data, session) - # Struct array - else: - data = StructArray.from_value(data, session) - - # Extract cells. - elif data.dtype.kind == 'O': - data = data.squeeze().tolist() - if not isinstance(data, list): - data = [data] - data = _extract_data(data, session) - - # Compress singleton values. - elif data.size == 1: - data = data.item() - - # Compress empty values. - elif data.size == 0: - if data.dtype.kind in 'US': - data = '' - else: - data = [] - - # Return parsed value. - return data - - def get_log(name=None): """Return a console logger. @@ -373,8 +70,6 @@ def get_log(name=None): http://docs.python.org/library/logging.html """ - import logging - if name is None: name = 'oct2py' else: @@ -387,11 +82,7 @@ def get_log(name=None): def _setup_log(): """Configure root logger. - """ - import logging - import sys - try: handler = logging.StreamHandler(stream=sys.stdout) except TypeError: # pragma: no cover @@ -404,4 +95,3 @@ def _setup_log(): _setup_log() -