Closed tko22 closed 5 years ago
Hi, I like you project and the way you organize your code ! Since few weeks I'm learning back-end. And few days ago I have done a recursive SQL alchemy to_dict. But i did this directly by creating my own model instead of creating a Mixin class, so my function it might require modifications.
So i have init my flask app like this :
from flask_sqlalchemy import SQLAlchemy
from backend.MyModel import MyModel
app = Flask( #...)
#...
db = SQLAlchemy(app, model_class=MyModel)
For the next days i ll try to handle your boilerplate, start to code REST api, then should have time to adapt my recursive function (unless you have already done it :)
class MyModel(object):
__abstract__ = True
query_class = None
query = None
max_depth_recursion = 2
DONOTSEND_MODEL = {'_sa_instance_state'}
DONOTSEND = []
_jsonified = None
modified = False
def __repr__(self):
return '<{}>'.format(self.__class__.__name__)
@property
def jsonify(self):
# if self._jsonified is None: # need using events
self._jsonified = self._to_dict_recursive(max_depth=1, depth=0, starter_obj=self)
return self._jsonified
@property
def to_dict_recursive(self, max_depth=None):
if max_depth is None:
max_depth = self.max_depth_recursion
return self._to_dict_recursive(max_depth=max_depth, depth=0, starter_obj=self)
def _to_dict_recursive(self, max_depth, depth, starter_obj):
depth += 1 # recursive depth
# functions
def anti_circular_recursion(attr):
if isinstance(starter_obj, attr.__class__) or depth >= max_depth:
# both choice possible
# return '<'+attr.__class__.__name__+'>'
return str(attr)
else:
return attr._to_dict_recursive(max_depth, depth, starter_obj)
result = {}
# __mapper__ is equivalent to db.inspect(self)
# but db (database) is not created yet cause we send this model to the constructor
for key in self.__mapper__.attrs.keys():
if key not in self.DONOTSEND:
attr = getattr(self, key)
if issubclass(type(attr), MyModel):
result[key] = anti_circular_recursion(attr)
elif isinstance(attr, list):
value = []
for obj in attr:
value.append(anti_circular_recursion(obj))
result[key] = value
# else : attr is not an instance of relationship (int, str..)
else:
result[key] = attr
return result
Yeah that's kind of messy ^^"
edit : then how i use my model (by the way, you should have notice that i used max deepth against circular relationships) :
from backend import db
class User(db.Model):
__tablename__ = 'user'
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(254), nullable=False, unique=True)
# pseudo = db.Column(db.String(50), nullable=False, unique=True)
prenom = db.Column(db.String(50))
nom = db.Column(db.String(50))
likes = db.Column(db.Integer, default=0)
password_hash = db.Column(db.String(350))
DONOTSEND = ['password_hash']
edit 2 : This the result of the recursive with deepth=5, here you can see the circurlar issue (only handled for the object which call the recursive function)
that's very cool! I haven't done this so go for it. For _init_.py
, I originally had initialized the SQLAlchemy instance there but realized there could be circular imports and thus I moved it in the models
module.
For the to_dict()
function, let's have a parameter depth
and have a default depth of 2
or 1
(I'm not sure which one would be better). This should help with the performance, especially since it is indeed recursive.
Yeah I like the place where you initialize the db instance, but i think we should create the model/mixin in the same file (base.py) or at least the same package (directory).
For the depth, i had a way to stop circular recursive relationship, but i was worried about performances. An other possibility would be to have 2 methods :
But the problem with depth parameter (as you can see on the screenshot i have shot) is that you may send too much data to a client.
That's why I'm actually learning about the cleanest way to create API. And with a REST API, we would have no need to send a recursive block of data, but only first depth with id-s of children. So a recursive block should be used only for back-end treatment. This point could seem obvious for you, but i think it is import to have in focus the final goal.
As soon I'll have time to spend on it, I'll insert the function to your project. I think I'll create the different options to facilitate the choice. Unfortunately, I have no experience on GitHub (and branch management), I ll need your advices to not screw up everything or even put a mess there ><.
Finally I did a lot of modifications on my fork. I invite you to see my abstraction, you might find few interesting concepts.
At least I would like to have your opinion on it !
completed with #22. Closing
the
to_dict
function in the classMixin
is used as a helper function in SQLAlchemy ORM models for converting the objects into python data structures for easy json serialization. However, it isn't recursive.For example, let's have a
Person
andEmail
table.Person
hasname
andemails
as fields whileEmail
hastotal_mail
,email_addr
,person
. Theemails
field is a one-to-many relationship to entries in Email table. The following happens when I runto_dict()
function: