I am working in Python 2.7 and trying to create a model structure which is something similar to django models but without database. Consider the following situation:
I want to create a model for Products (e.g CarryBag) which will have some predefined attributes of basic type (string, int, boolean etc), using which instances can be created. This model must inherit from an abstract model say 'GenericProduct' which will have certain defined functionality related to a product:
class GenericProduct:
name = StringType(required=True)
id = StringType(required=True)
def to_json(self):
....
def to_xml(self):
...
def some_other_function(self):
...
class CarryBag(GenericProduct):
material = StringType(required=True, default="Polyester", help_text="Type of material used")
price = IntType(required=True)
available = BooleanType(default=False)
So, challenges are:
This modelling structure must work very much similar to Django models but without database interaction.
How do I define abstract class 'GenericProduct'
How do I create a class that defines datatype classes 'IntType', 'StringType' & 'BooleanType' which can be used to define attributes.
What is the best way to achieve this keeping in mind python constructs. I am trying to go through django db model code to get some sense about it. Following is my code for one of the datatype classes:
class StringType(object):
def __init__(self, default=None, required=False, help_text=None):
self.required = required
self.help_text = help_text
if default:
self.__set__(self,default)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("'{}' must be a string.".format(value))
self.value = value
And this is how I was using it:
class GenericProduct:
name = StringType(required=True) # WRONG: This creates a static member of the class
.....
Related
Currently in a situation where I have a series of classes that turn an API request JSON into objects. The objects are modeled after my database schema. I think the part that I'm struggling with is how to represent those entity relationships that are formed with foreign keys in my database.
The following classes are just for an example, the instance variables are much different for my application's schema.
class Table(ABC):
def __init__(self):
# stuff
#abstractmethod
def validateSchema(self):
"""Validates the resources column values."""
pass
class ClassRoom(Table):
def __init__(self, id, location_id, location):
super().__init__()
self.id = id
self.location = Location(location_id, location)
def validateSchema(self):
# stuff
class Location(Table):
def __init__(self, id, location):
super().__init__()
self.id = id
self.location = location
def validateSchema(self):
# stuff
The part I'm concerned about is when I am creating an object of the same type as the class that has the object as an instance variable.
class ClassRoom(Table):
def __init__(self, id, location_id, location):
# Can I instantiate this class even if it inherits the same parent?
self.location = Location(location_id, location)
Is this ok in OOP? Is there a better way to design my classes?
Also, these classes are just defined for the request JSONs that get sent to my API. Their purpose will be to facilitate column validation and a few other purposes. The specific validation I am hoping to implement in these classes comes from this other Stackoverflow post Flask sqlAlchemy validation issue with flask_Marshmallow. I'm not trying to recreate SqlAlchemy here.
Your Table class is analogous to SqlAlchemy's db.Model class. And just as it can have references between different subclasses, so can you.
The specific design of your Classroom.__init__() method seems wrong. All the classrooms in the same location should have references to the same Location object, but you create a new one for each classroom. The Location should be a parameter, rather than the location ID and name.
class ClassRoom(Table):
def __init__(self, id, location):
super().__init__()
self.id = id
self.location = location
Then you can create multiple classrooms in a location:
loc = Location(loc_id, loc_name)
c1 = Classroom(c1_id, loc)
c2 = Classroom(c2_id, loc)
I have a model with a field is_deleted, now I want all forms of query for this model to always filter by is_deleted=False in addition to whatever filtering arguments is passed to .filter and .filter_by.
In Django, I would normally override the manager and add my own filtering but I need help for SQLAlchemy.
UPDATE:
I ended-up doing the following:
class CustomQuery(Query):
def __new__(cls, *args, **kwargs):
if args and hasattr(args[0][0], "is_deleted"):
return Query(*args, **kwargs).filter_by(is_deleted=False)
else:
return object.__new__(cls)
session = scoped_session(sessionmaker(query_cls=CustomQuery))
It works but if I have more fields later on I imagine I'll have to add more condition, there must be a way to do this on the model level.
This is a very old question so I'm sure the OP solved their issue, but as it remains unanswered (in 2021) here's how we've approached applying a custom query class to all models:
Define the custom query as above
class CustomQuery(Query): ...
Then set this query class as the query attribute on your base model class:
class BaseModel(Model):
__abstract__ = True
query_class = CustomQuery
...
Then any models implementing the BaseModel will obviously inherit this behaviour:
class MyModel(BaseModel):
__tablename__ = 'my_model'
....
Note, in our case not all of the tables follow the soft delete pattern (we don't care about the history of every single table). Here, you could implement a separate BaseModel that uses the default query class.
class ImmutableBaseModel(Model):
__abstract__ = True
query_class = CustomQuery
...
class MutableBaseModel(Model):
__abstract__ = True
If you find yourself here and you've not read it yet check out this excellent blog post from Miguel Grinberg on implementing the soft delete pattern and accompanying repo
The app has such logic: list of people stored in the database, each man has a rating calculated in realtime and this value is never stored in database. And I want to use one class to work with dababase fields: name, age etc. and non database field: rating.
Is it possible in sqlalchemy? Now I'm using inheritance Man -> ManMapping:
class Man:
rating = None
def get_rating(self):
return self.rating
...
class ManMapping(Base, Man):
__tablename__ = 'man'
id = Column('man_id', Integer, primary_key=True)
name = Column(Unicode)
...
It works but it looks terrible for me. Is it right approach or I have to do something else?
This is the correct solution: https://docs.sqlalchemy.org/en/13/orm/constructors.html
Hybrid properties are somewhat less flexible that this. The accepted answer is not an actual answer to the problem.
The SQLAlchemy ORM does not call init when recreating objects from database rows. The ORM’s process is somewhat akin to the Python standard library’s pickle module, invoking the low level new method and then quietly restoring attributes directly on the instance rather than calling init.
If you need to do some setup on database-loaded instances before they’re ready to use, there is an event hook known as InstanceEvents.load() which can achieve this; it is also available via a class-specific decorator called reconstructor(). When using reconstructor(), the mapper will invoke the decorated method with no arguments every time it loads or reconstructs an instance of the class. This is useful for recreating transient properties that are normally assigned in init:
from sqlalchemy import orm
class MyMappedClass(object):
def __init__(self, data):
self.data = data
# we need stuff on all instances, but not in the database.
self.stuff = []
#orm.reconstructor
def init_on_load(self):
self.stuff = []
If you are using any data from the DB to calculate rating I would recommend looking at hybrid property. Otherwise I would add self.rating to init and have your function inside the ManMapping class. Something like:
class ManMapping(Base):
__tablename__ = 'man'
id = Column('man_id', Integer, primary_key=True)
name = Column(Unicode)
def __init__(self)
self.rating = None
def get_rating(self):
return self.rating
In my point of view, you should have two distincts classes.
One for the logic in your code and one to communicate with your DB.
class Man(object):
"""This class is for your application"""
def __init__(self, name, rating):
# If the identifier is only used by the DB it should not be in this class
self.name = name
self.rating = rating
class ManModel(Base):
"""This model is only to communicate with the DB"""
__tablename__ = 'man'
id = Column('man_id', Integer, primary_key=True)
name = Column(Unicode)
You should have a provider that does queries to DB with ManModel objects, then maps results to Man objects and return your mapped data to the caller.
Your application will only use Man objects and your provider will do the mapping.
Something like below :
class DbProvider(object):
def get_man(self, id):
man_model = session.query(ManModel).filter(ManModel.id == id).one_or_none()
return self.man_mapper(man_model) if man_model else None
def get_men(self):
men_model = session.query(ManModel).all()
return [self.man_mapper(man_model) for man_model in men_model]
def man_mapper(self, man_model):
return Man(man_model.name, self.calculate_rating(man_model))
class Test(object):
def display_man(self):
man = db_provider.get_man(15)
if man:
print man.name, man.rating
I have a django model that I want to attach an extra piece of information to, depending on the environment the instance is in (which user is logged in). For this reason, I don't want to do it at the database level.
Is this okay to do? Or are there problems that I don't foresee?
in models.py
class FooOrBar(models.Model):
"""Type is 'foo' or 'bar'
"""
def __init__(self, type):
self.type = type
in views.py
class FooCheck(FooOrBar):
"""Never saved to the database
"""
def __init__(self, foo_or_bar):
self.__dict__ = foo_or_bar.__dict__.copy()
def check_type(self, external_type):
if external_type == 'foo':
self.is_foo = True
else:
self.is_foo = False
foos_or_bars = FooOrBar.objects.all()
foochecks = map(FooCheck, foos_or_bars)
for foocheck in foochecks:
foocheck.check_type('foo')
extra credit question: Is there a more efficient way of calling a method on multiple objects i.e. replacing the last forloop with something clever?
Okay, this does not work. Trying to delete a FooOrBar objects throws a complaint about
OperationalError at /
no such table: test_FooCheck
To get around this I'm just not going to inherit from FooOrBar, but if anyone has a suggestion on a better way to do it I'd be interested in hearing it
I had a similar issue, I did something like:
class Foo(models.Model):
# specific info goes here
class Bar(models.Model):
# specific info goes here
class FooBar(models.Model):
CLASS_TYPES = {
"foo":Foo,
"bar":Bar
}
type = models.CharField(choices=CLASS_TYPES)
id = models.IntegerField()
#field to identify FooBar
then you can get the object back using
object = FooBar.CLASS_TYPES[instance.type].objects.get(id=instance.id)
where instance is the FooBar instance
I was going through code of https://github.com/hit9/CURD.py/blob/master/CURD.py which is a simple orm that performs normal curd operations .. and i could not understood part of code which goes like this(on line number 616):
.....#smthing #..
for name, attr in cls.__dict__.iteritems():
if isinstance(attr, Field):
attr.describe(name, cls)
fields[name] = attr
what does attr.describe(attr, Field) do ? I googled it out but found nothing.
It's not a Python language feature, it's a method on that library. You can see the definition here:
https://github.com/hit9/CURD.py/blob/master/CURD.py#L251
class Field(Leaf):
"""
Field object.
Field examples: User.name, User.age ..
"""
def __init__(self, is_primarykey=False, is_foreignkey=False):
self.is_primarykey = is_primarykey
self.is_foreignkey = is_foreignkey
# describe model's attr
def describe(self, name, model):
self.name = name
self.model = model
# fullname e.g. : User.id 's fullname is "user.id"
self.fullname = self.model.table_name + "." + self.name
# describe the attribute, reload its access control of writing, reading
setattr(model, name, FieldDescriptor(self))
setattr sets an attribute on an object. So if I call describe("field_name", myObject), it will set myObject.field_name to the description of the model. Or something like that.
That is not a python standard thing.
The loop iterates through the names and values of a class, and the attributes of that class that are instances of a Field type are added to a dictionary.
Field is not part of python standard library, you should search that project for the Field class.