On Mon, Jul 1, 2019, at 5:56 AM, gamcil wrote:
> Hi,
> 
> I'm new to SQLAlchemy/ORMs - previously I had just been interacting directly 
> with the builtin SQLite driver and had built up my own mess of a mapping 
> system.
> 
> 1) I'm starting to convert some classes in a Python package to SQLAlchemy 
> models. . A paired down example of what I'm doing:
> 
> class Gene:
> def __init__(self, identifiers, features):
> self.identifiers = identifiers
> ` self.features = features``
> 
> gene = Gene(identifiers={'locus': 'GENE_0001', 'protein_id': 'PROT_0001'},
>  features={'mRNA': '1..300,400..500', 'CDS': '1..300,400..500'})`
> 
> In reality, I'm dealing with many attributes that are all related to the Gene 
> and stored in the same table, which is why to simplify the interface of my 
> classes, I grouped related instance attributes in dictionaries. For example, 
> the location of the mRNA feature of this Gene can then be accessed by 
> gene.features['mRNA']. Each value in the dictionary refers to a unique 
> database column.
> 
> However, when creating SQLAlchemy models, it's required to explicitly map 
> Columns directly as class attributes, e.g.:
> `class Gene:
>  id = Column(Integer, primary_key=True)
>  locus = Column(String)`
> ` protein_id = Column(String)
>  mRNA = Column(String)
>  CDS = Column(String)`
> 
> Is there a simple way to provide the dictionary functionality? Something like:
> `class Gene:
>  id = Column(Integer, primary_key=True)
>  identifiers = {
>  'locus': Column(String),`
> ` 'protein': Column(String)
>  }
>  ...```
> Where a change in Gene.identifiers['locus'] would then result in an update to 
> the 'locus' column. I saw MutableDict in the documentation but it seems 
> non-trivial to implement for something like this.
> Is this even a good idea?

I'm going to focus on #1 here, so, this kind of thing is pretty straightforward 
in Python, throughout SQLAlchemy's examples / extensions there are all kinds of 
"magic dictionary" kinds of examples going on, and this one is...another one :) 
To give a high level for this kind of thing, as far as setting up mappings / 
tables, that can always be done "magically", that is, you can write event hooks 
to read any kind of configuration you want that will set up the mapper() / 
Table with whatever columns are needed. For the "get / set" a dictionary part, 
we always use a Python descriptor for that kind of thing, so in this case I'd 
likely be building a custom kind of descriptor class that also serves as the 
information to generate the table definition. Finally, you want the dictionary 
to be "live", that is, not get /set of the whole dictionary, you want changes 
in the dictionary to affect the model. So for the last part, yes there needs to 
be a custom dictionary object of some kind. It would use the same Python 
techniques as MutableDict but it likely would be specific to the case here, and 
overall this case is not that "hard". In this case I would create a Python 
object that is not in fact a "dict", but just an object that has as dict-like 
interface, e.g. __getitem__(), __setitem__(), keys(), etc., and then it just 
uses the object that it comes from as the source of data, like:


class GeneView:
 def __init__(self, gene, attrs):
 self.gene = gene
 self.attrs = attrs

 def keys(self):
 return iter(self.attrs)

 def items(self):
 return ((attr, getattr(self.parent, attr)) for attr in self.attrs)

 def __getitem__(self, key):
 return getattr(self.parent, key) # maybe you want to check self.attrs too

 def __setitem__(self, key, value):
 setattr(self.parent, key, value) # mutability


The above thing comes from a descriptor, a hardcoded version would look like:

class Gene(...):
 @property
 def identifiers(self):
 return GeneView(self, ['locus', 'protein'])


I'd then want to make the above "hardcoded" pattern more generic making a 
custom descriptor class 
(https://docs.python.org/3/howto/descriptor.html#descriptor-example) so that 
the mapping looks like:


class Gene(...):
 identifiers = gene_view_attr('locus', 'protein')


to map it, a simple event hook like before_mapper_configured (or declararive's 
declare_first / declare_last hooks: 
https://docs.sqlalchemy.org/en/13/orm/extensions/declarative/api.html?highlight=declare_last#declare-last)
 can be used, in this case it probably doesn't matter where in the mapping 
phase we are since columns can be added at any time:

from sqlalchemy import event

@event.listens_for(Base, "before_mapper_configured", propagate=True)
def setup_view_attrs(mapper, class_):
 for value in class_.__dict__.values():
 if isinstance(value, gene_view_attr): # look for our descriptor
 for attr in value.attrs:
 setattr(class_, attr, Column(attr, String)) # map columns


That's all you need for #1, let me know if you need more detail.




> 
> 2) Say for the example above I have created a plain Python Location class to 
> represent a location of a feature in self.features:
> class Location:
>  def __init__(self, intervals, ...):
>  self.intervals = intervals
> ` ...
> 
>  @classmethod
>  def from_flat(cls, flat):
>  # convert flat location to list of tuples
>  # e.g. [(1, 300), (400, 500)]`
> 
> This class has a classmethod that takes the stored flat string (e.g. 
> 1..300,400..500) and converts it to a list of interval tuples, and has some 
> extra logic in it for use in my applications. I understand I can use 
> orm.reconstructor to instantiate these Location objects from the stored 
> attributes upon object loading, i.e.
> @reconstructor
> def init_on_load(self):
>  self.mRNA = Location.from_flat(self.mRNA)
>  ...
> 
> 
> However, what I don't get is how I should be shuttling changes in these 
> objects back to the database as updates.
> Is there some way to map this (instantiate Location on loading; mutate 
> Location; convert Location back to flat format to update the Gene attribute)? 
> Do i need to write setter methods for each that directly set the underlying 
> column attribute to the flat representation of the Location?

So I usually use descriptors for this kind of thing too, like above. There is 
also the possibility of using custom datatypes that deal with the tuples and 
convert to strings for the database side. If your application never wants to 
see the "string" form and just wants to see tuples, you can make a 
TypeDecorator that does this, see the examples at 
https://docs.sqlalchemy.org/en/13/core/custom_types.html#typedecorator-recipes 
. Then again if you are going to use the dictionary approach above for these 
attributes, you can build the coercion between string / tuple into the GeneView 
class directly. I'd probably do it that way in this case assuming that's the 
place you want to see the tuples.



> 
> Any help is appreciated, cheers
> 

> --
>  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.
>  To view this discussion on the web visit 
> https://groups.google.com/d/msgid/sqlalchemy/f3105bee-b6dc-489c-82e0-c6d36710a462%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/sqlalchemy/f3105bee-b6dc-489c-82e0-c6d36710a462%40googlegroups.com?utm_medium=email&utm_source=footer>.
>  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.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/6f308181-88cb-4271-bde5-2b69b11a3671%40www.fastmail.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to