Ok, thank you for the fast answer as always. Moreover the specs for the JSON datatype for MySQL say:
A JSON column cannot have a default value. So I think I will just skip the default value. Thank you by the way again! _ -. .´ |∞∞∞∞ ', ; |∞∞∞∞∞∞ ˜˜ |∞∞∞∞∞∞∞∞∞ RdB ,., |∞∞∞∞∞∞ .' '. |∞∞∞∞ -' `’ https://rdb.is On 30 agosto 2018 a 17:09:33, Mike Bayer (mike...@zzzcomputing.com) scritto: On Thu, Aug 30, 2018 at 6:32 AM, Ruben Di Battista <rubendibatti...@gmail.com> wrote: > Ehi Mike, thank you :). > > I just went ahead in implementing a custom type for a Python object that is > a (subclass of) numpy array and that is stored as JSON in the DB. > > This is the Python class: > > class ElevationMask(np.ndarray): > r""" A class to represent an elevation mask. > > This class proxies the functionality of a numpy array encoding the > elevation mask as a functional dependency of the type: > > .. math:: > > \epsilon_\text{mask} = \epsilon_\text{mask}(\theta) > > So the elevation mask is described as the mapping > :math:`\epsilon_\text{mask}` that for each value of azimuth angle > :math:`\theta` maps the corresponding elevation angle value > > > Args: > elevation_mask(numpy.ndarray): The elevation mask as numpy array. > It's > an 2xN array. First row is the azimuth values. Second row is > elevation values > Attributes: > azimuth(numpy.ndarray): The values of the azimuth angles that are > part > of the elevation mask > elevation(numpy.ndarray): The corrisponding elevation values to the > azimuth values > """ > > def __new__(cls, elevation_mask): > obj = np.asarray(elevation_mask).view(cls) > rows, cols = obj.shape > > if not(rows == 2): > raise IndexError("The elevation mask array given has not the " > "right shape. It needs to be a 2xN array.") > > return obj > > @property > def azimuth(self): > return self[0, :] > > @property > def elevation(self): > return self[1, :] > > def __call__(self, azimuth_value): > > # Interpolation bounds checking > if azimuth_value < self.azimuth.min() or \ > azimuth_value > self.azimuth.max(): > raise ValueError("The required azimuth " > "value is out of interpolation bounds ") > > return np.interp(azimuth_value, > self.azimuth, > self.elevation) > > @staticmethod > def _to_json(obj): > """ This method returns the dict representation for JSON encoding > """ > > return { > '__type__': type(obj).__name__, > 'entries': { > 'azimuth': obj.azimuth.tolist(), > 'elevation': obj.elevation.tolist() > } > } > > def to_json(self): > """ This actually dumps the instance into the JSON string """ > > return json.dumps(self, default=self._to_json) > > @staticmethod > def _from_json(json_dict): > """ This reconstitutes the Python object from the JSON serialization > """ > if '__type__' in json_dict: > if json_dict['__type__'] == ElevationMask.__name__: > mask_entries = json_dict['entries'] > azimuth = mask_entries['azimuth'] > elevation = mask_entries['elevation'] > > el_mask_array = np.array([ > azimuth, > elevation > ]) > > return ElevationMask(el_mask_array) > > return json_dict > > @classmethod > def from_json(self, json_dict): > return json.loads(json_dict, object_hook=self._from_json) > > > So it's basically a 2xN array. > > Then I have the actual custom type > > from sqlalchemy.dialects.mysql import JSON > > class JSONElevationMask(TypeDecorator): > """ This type emits a JSON object into the Database. When instead > loading > the JSON from the database, it transforms the JSON in a ElevationMask > object (i.e. a numpy-like object with few additional features """ > > impl = JSON > > def process_bind_param(self, value, dialect): > if value is not None: > return value.to_json() > > def process_load_param(self, value, dialect): > if value is not None: > return ElevationMask.from_json(value) > > It seems to work as expected. The only problem I'm experiencing is when I > setup a default value for a Column of this type: > > elevation_mask = Column('elevationMask', JSONElevationMask, > default=ElevationMask([ > [0, 2*PI], > [0, 0]]) > ) it looks like that object you have has many special behaviors, including __new__, __call__, and is also a descendant of a C object, so some combination of those things which I can't exactly reproduce here is causing SQLAlchemy's check for passing of a "callable" to fail. The "default" parameter of a Column can accept a Python callable, so this object can't be passed as is since it is already a non-compatible callable. Your best bet is to just pass the JSON itself as a SQL object, e.g. text("'%s'" % ElevationMask(...).to_json()). SQLAlchemy will use the text() construct as is to generate a SQL string that will be inline with the INSERT statement. > > When I do this, I get > File > "/Users/rubendibattista/.envs/pyggdrasill/lib/python2.7/site-packages/sqlalchemy/sql/schema.py", > line 1259, in __init__ > args.append(ColumnDefault(self.default)) > File > "/Users/rubendibattista/.envs/pyggdrasill/lib/python2.7/site-packages/sqlalchemy/sql/schema.py", > line 2067, in __init__ > arg = self._maybe_wrap_callable(arg) > File > "/Users/rubendibattista/.envs/pyggdrasill/lib/python2.7/site-packages/sqlalchemy/sql/schema.py", > line 2100, in _maybe_wrap_callable > argspec = util.get_callable_argspec(fn, no_self=True) > File > "/Users/rubendibattista/.envs/pyggdrasill/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", > line 330, in get_callable_argspec > return get_callable_argspec(fn.__call__, no_self=no_self) > File > "/Users/rubendibattista/.envs/pyggdrasill/lib/python2.7/site-packages/sqlalchemy/util/langhelpers.py", > line 317, in get_callable_argspec > if no_self and (_is_init or fn.__self__): > ValueError: The truth value of an array with more than one element is > ambiguous. Use a.any() or a.all() > > That is because a `numpy` array cannot be checked for truth. Is there a way > to monkey patch that `if no_self` in order to use the right method for the > array to be true (so .any() or .all())? > > > On Monday, July 23, 2018 at 7:51:15 PM UTC+2, Mike Bayer wrote: >> >> On Mon, Jul 23, 2018 at 1:28 PM, Ruben Di Battista >> <rubendi...@gmail.com> wrote: >> > Hello, >> > I need to store a matrix into the database and I was evaluating the >> > possibility to have a column of JSON type in MySQL to store it. What I >> > would >> > like to achieve is the possibility of operating on a numpy array when >> > manipulating that column on Python while keeping a "meaningful" data >> > type on >> > the DB, if possible. >> > >> > I was reading the Custom Types section of the documentation and, even >> > though >> > the application I envisage looks like just an extension to the JSON >> > datatype, I believe to have understood that the `TypeDecorator` approach >> > can't store a state (i.e. the numpy array). Is that correct? So do I >> > need to >> > use the `UserDefined` subclassing method? >> >> when you want to use a special kind of datatype on the Python side, >> and there is already a SQLAlchemy type on the database side that does >> what you want, you use TypeDecorator. Types don't "store" states, >> database columns do - you'd need to define how to marshal your numpy >> array into JSON and back out again. you don't need to use >> UserDefinedType. If you can create numpy->json and json->numpy >> functions, I can show you how to stick that onto TypeDecorator. >> >> >> >> >> >> >> >> > >> > Thanks in advance, >> > >> > -- >> > SQLAlchemy - >> > The Python SQL Toolkit and Object Relational Mapper >> > >> > http://www.sqlalchemy.org/ >> > >> > To post example code, please provide an MCVE: Minimal, Complete, and >> > Verifiable Example. See http://stackoverflow.com/help/mcve for a full >> > description. >> > --- >> > You received this message because you are subscribed to the Google >> > Groups >> > "sqlalchemy" group. >> > To unsubscribe from this group and stop receiving emails from it, send >> > an >> > email to sqlalchemy+...@googlegroups.com. >> > To post to this group, send email to sqlal...@googlegroups.com. >> > Visit this group at https://groups.google.com/group/sqlalchemy. >> > For more options, visit https://groups.google.com/d/optout. > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at https://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout. -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.