Data in JSON Column as normal column - python

I have a lot of unstructured data in Column(JSONB).
I would like to "standardize" this data and access it like normal Columns.
This actually does work:
class Order(Base):
__tablename__ = "orders"
id_= Column(Integer, primary_key=True, nullable=False)
type = Column(String)
data = Column(JSONB)
#hybrid_property
def email(self):
return self.data['email']
#email.setter
def email(self, value):
self.data['email'] = value
#email.expression
def email(cls):
return cls.data['email'].astext
However data can have different keys, eg: email, e_mail, email_address, etc..
The solution I'm thinking about is to "extend" main class with another class depending
on value in "type":
class Parser1:
#hybrid_property
def email(self):
return self.data['email']
#email.setter
def email(self, value):
self.data['email'] = value
#email.expression
def email(cls):
return cls.data['email'].astext
class Parser2:
#hybrid_property
def email(self):
return self.data['email_address']
#email.setter
def email(self, value):
self.data['email_address'] = value
#email.expression
def email(cls):
return cls.data['email_address'].astext
class Order(Base):
__tablename__ = "orders"
id_= Column(Integer, primary_key=True, nullable=False)
type = Column(String)
data = Column(JSONB)
if type == 'one':
extend with class "Parser1"
elif type == 'two':
extend with class "Parser2"
Would this work or is it stupid idea?
Do you have any better solution?
Any hints very welcome.

Related

Flask Admin and SQLAlchemy relationship advice, how do I get columns from a grandparent table?

Using Flask Admin, I'm having difficulties understanding how to retrieve columns from parent and grandparent tables to use for filtering / create dropdowns
class Project(db.Model):
project_id=db.Column(db.Integer, primary_key=True)
title=db.Column(db.String(100), nullable=False)
def __repr__(self):
return self.title
def __str__(self):
return self.title
def get_id(self):
return self.project_id
class Story(db.Model):
story_id=db.Column(db.Integer, primary_key=True)
description=db.Column(db.String(), nullable=False)
project_id=db.Column(db.Integer, db.ForeignKey(Project.project_id))
project=db.relationship(Project)
def __repr__(self):
return self.description
def __str__(self):
return self.description
def get_id(self):
return self.story_id
class Task(db.Model):
task_id=db.Column(db.Integer, primary_key=True)
description=db.Column(db.String(), nullable=False)
story_id=db.Column(db.Integer, db.ForeignKey(Story.story_id))
story=db.relationship(Story)
def __repr__(self):
return self.description
def __str__(self):
return self.description
def get_id(self):
return self.task_id
class TaskModelView(ModelView):
create_modal = False
edit_modal = False
can_set_page_size = True
page_size = 20
column_display_pk = True
column_display_all_relations = True
admin.add_view(TaskModelView(Task, db.session))
When dealing with the Tasks list, I see the Story description with no problems and can use that to filter the list but how would I obtain the Project title and be able to filter on that column?
Have been searching the docs but obviously missing something ..
Maybe this can help.
class TaskModelView(ModelView):
...
column_list = [ 'description', 'story', ]
column_filters = ['description', 'story.description', ]
column_labels = {'story.description': 'Story Desc.'}
References here.

How to decorate a class so that i could be able to change the attribute of a class in run time

def decorator(cls):
#code
return cls
#decorator
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20),nullable=False)
ssid = db.Column(db.String(20))
def __repr__(self):
return f"User('{self.username}',{self.password})"
I want to decorate a class such that i could be able to access the value of ssid in decorator function and add a new attribute to the class.As the new attribute requires the value of ssid.
user = User(username='prince',ssid='9734ait')
db.session.add(user)
This doesn't seem like an appropriate use case for a decorator... seems to me you can just use inheritance and add a new attribute in the __init__. For instance:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20),nullable=False)
ssid = db.Column(db.String(20))
def __init__(self, *args, password=None, your_new_attribute=None, **kwargs):
super().__init__(*args, **kwargs)
self.password = hash(ssid)
self.your_new_attribute = your_new_attribute
def __repr__(self):
return f"User('{self.username}',{self.password})"
If you insist on using a decorator:
class Decorator:
def __call__(self, cls):
class Inner(cls):
cls.password = cls.ssid[::-1]
return Inner
#Decorator()
class User:
ssid = "fooo"
def __repr__(self):
return f"User({self.ssid}, {self.password})"
u = User()
print(u)
Output:
User(fooo, ooof)
Would defining a property within the decorator be sufficient for your use case ?
For example:
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///site.db"
db = SQLAlchemy(app)
class Encryptor:
def __call__(self, cls):
class Inner(cls):
# define a getter function to return the password
def password_getter(self):
# return the calculated password here now you have access to username and ssid
return f'{self.username} - {self.ssid}'.upper()
setattr(cls, "password", property(fget=password_getter))
return Inner
#Encryptor()
class User5(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(20), nullable=False)
ssid = db.Column(db.String(20))
def __repr__(self):
return f"User('{self.username}',{self.ssid}; PASSWORD: {self.password})"
db.create_all()
user = User5(username="prince", ssid="3456ait")
db.session.add(user)
db.session.commit()
users = User5.query.all()
print(users)
In a comment, you said that the actual goal is to encrypt passwords on the way in and out of the class. Sqlalchemy offers this using hybrid properties. This is an example from one of my projects-
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
username = Column(String(255))
hashed_password = Column("password", String(255))
#hybrid_property
def password(self):
return self.hashed_password
#password.setter # type: ignore
def password(self, value):
rounds = 4
if not isinstance(value, bytes):
value = value.encode("utf-8")
self.hashed_password = hashpw(value, gensalt(rounds)).decode("utf-8")
(so only the hashed password is stored in the database in this case- to check the password, you hash the input and compare it to user.password)

Sqlalchemy model with enum column type

I'm trying to store my python enum in a table. I tried following a stackoverflow post but now I'm getting this error:
File "c:\users\arrchana\pycharmprojects\be\venv\lib\site-packages\sqlalchemy\dialects\postgresql\base.py", line 2231, in format_type
raise exc.CompileError("PostgreSQL ENUM type requires a name.")
sqlalchemy.exc.CompileError: PostgreSQL ENUM type requires a name.
This is my code:
from appInits.db import db
from enums.goalTypes import GoalTypes
from sqlalchemy import types
class EnumAsInteger(types.TypeDecorator):
"""Column type for storing Python enums in a database INTEGER column.
This will behave erratically if a database value does not correspond to
a known enum value.
"""
impl = types.Integer # underlying database type
def __init__(self, enum_type):
super(EnumAsInteger, self).__init__()
self.enum_type = enum_type
def process_bind_param(self, value, dialect):
if isinstance(value, self.enum_type):
return value.value
raise ValueError('expected %s value, got %s'
% (self.enum_type.__name__, value.__class__.__name__))
def process_result_value(self, value, dialect):
return self.enum_type(value)
def copy(self, **kwargs):
return EnumAsInteger(self.enum_type)
class Tasks(db.Model):
__tablename__ = 'tasks'
id = db.Column(db.Integer, primary_key=True, unique=True)
text = db.Column(db.Text, unique=True, nullable=False)
difficulty = db.Column(db.Integer, unique=False, nullable=False)
tool = db.Column(EnumAsInteger(GoalTypes), nullable=False)
def __repr__(self):
return '<Task {}: {}, {}, {}>'.format(self.id, self.difficulty, self.tool, self.text)
def __init__(self, id, text, difficulty, tool):
self.id = id
self.text = text
self.difficulty = difficulty
self.tool = tool
This is the stack post:
Sqlalchemy database int to python enum

Remove a relation many-to-many (association object) on Sqlalchemy

I'm stuck with a SqlAlchemy problem.
I just want to delete an relation. This relation is made by an association object.
models
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
username = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
following = db.relationship('Follower', foreign_keys='Follower.user_id')
followed_by = db.relationship('Follower', foreign_keys='Follower.follow_user_id')
def __repr__(self):
return '<%s (%i)>' % (self.username, self.id)
class Follower(db.Model):
__tablename__ = 'followers'
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
follow_user_id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True)
created_at = db.Column(db.DateTime, default=datetime.datetime.now)
user_followed = db.relationship("User", primaryjoin=(follow_user_id==User.id))
user = db.relationship("User", primaryjoin=(user_id==User.id))
def __repr__(self):
return '<%i %i>' % (self.user_id, self.follow_user_id)
How I add a relation (it works !):
u1 = # user 1
u2 = # user 2
...
f = Follower()
f.user_followed = u2
u1.following.append(f)
db.session.commit()
How I try do delete a relation (it doesn't work):
f = Follower()
f.user_followed = u2
u1.following.remove(f)
db.session.commit()
The error
ValueError: list.remove(x): x not in list
I understand why it doesn't work, it's because this Follower() instance is not in the list u1.following. So, how can I delete this relation?
You can override __eq__, __ne__, and __hash__ so that instances that are not the same instance, but have the same values, compare and hash equal.
I use the following mixin for this purpose. Just override compare_value in the subclass to return whatever should actually be compared.
from sqlalchemy import inspect
class EqMixin(object):
def compare_value(self):
"""Return a value or tuple of values to use for comparisons.
Return instance's primary key by default, which requires that it is persistent in the database.
Override this in subclasses to get other behavior.
"""
return inspect(self).identity
def __eq__(self, other):
if not isinstance(other, self.__class__):
return NotImplemented
return self.compare_value() == other.compare_value()
def __ne__(self, other):
eq = self.__eq__(other)
if eq is NotImplemented:
return eq
return not eq
def __hash__(self):
return hash(self.__class__) ^ hash(self.compare_value())
One could also try querying for the object first and then delete it from the list.
follower_to_be_deleted = db.session.query(Follower).filter_by(user_id=u2.id).first()
u1.following.remove(follower_to_be_deleted)
db.session.commit()

SQLAlchemy declarative. Specify columns to select

Declarative base:
Base = declarative_base()
Base.query = Session.query_property()
The class:
class Cheat(Base):
__tablename__ = 'cheats'
id = Column(Integer, primary_key = True, autoincrement = True)
cheat = Column(Text)
name = Column(String(255), index = True)
_html = Column('html', Text)
_slug = Column('slug', String(255))
#hybrid_property
def html(self):
return self._html
#html.setter
def set_html(self, md):
from markdown import markdown
self._html = markdown(md)
#hybrid_property
def slug(self):
return self._slug
#slug.setter
def set_slug(self, name):
self._slug = slugify(name)
def __init__(self, name, cheat):
self.name = name
self.slug = name
self.cheat = cheat
self.html = cheat
def __repr__(self):
return "Cheat<%s>" % self.name
Now I can get everything from the cheats:
Cheat.query.all()
and SQLAlchemy will generate SQL statement similar to:
SELECT name, slug, cheat, html FROM cheats
but I want my SQL statement to be:
SELECT name, slug FROM cheats
so I need to specify which columns I want to retrieve, because I don't really need to pull heavy texts from the database over the network.
How do I do that?
define them as deferred, then they will only be fetched when the're accessed
from sqlalchemy.orm import deferred
class Cheat(Base):
__tablename__ = 'cheats'
id = Column(Integer, primary_key = True, autoincrement = True)
cheat = deferred(Column(Text))
name = Column(String(255), index = True)
_html = Column('html', Text)
_slug = deferred(Column('slug', String(255)))
for name, slug in session.query(Cheat.name, Cheat.slug):
print name, slug

Categories