How to query additional databases using cursor in Django Pytests - python

I am developing a Django app (Django v3.2.10, pytest v7.0.1, pytest-django v4.5.2) which uses cursor to perform raw queries to my secondary DB: my_db2, but when running tests, all the queries return empty results, like if they were running on parallel transactions.
My test file:
#pytest.mark.django_db(transaction=True, databases=['default', 'my_db2'])
class TestItems:
def test_people(self):
person1 = PeopleFactory() # Adds 1 person to my_db2
assert fetch_all_persons() == 1 # Fails Returns 0
My Factory:
class PeopleFactory(factory.django.DjangoModelFactory):
id = factory.Sequence(lambda x: x + 1)
name = factory.Faker('first_name')
class Meta:
model = People
My function:
from django.db import connections
def fetch_all_persons():
with connections['my_db2'].cursor() as cursor:
cursor.execute(f"SELECT * FROM Persons")
return len(list(cursor.fetchall())):
According documentation transaction=True should prevent this issue, but it doesn't, does somebody know how to fix it?
Note.- Using the ORM is not an option, this is just a simplified example to represent the issue. The real queries used are way more complex.

#hoefling and #Arkadiusz Łukasiewicz were right, I just needed to add the corresponding DB within the factories:
class PeopleFactory(factory.django.DjangoModelFactory):
id = factory.Sequence(lambda x: x + 1)
name = factory.Faker('first_name')
class Meta:
model = People
database = 'my_db2'
Thank you both.

Related

peewee check automatically created id not in result of subquery

I have next data structure:
from enum import IntEnum, unique
from pathlib import Path
from datetime import datetime
from peewee import *
#unique
class Status(IntEnum):
CREATED = 0
FAIL = -1
SUCCESS = 1
db_path = Path(__file__).parent / "test.sqlite"
database = SqliteDatabase(db_path)
class BaseModel(Model):
class Meta:
database = database
class Unit(BaseModel):
name = TextField(unique=True)
some_field = TextField(null=True)
created_at = DateTimeField(default=datetime.now)
class Campaign(BaseModel):
id_ = AutoField()
created_at = DateTimeField(default=datetime.now)
class Task(BaseModel):
id_ = AutoField()
status = IntegerField(default=Status.CREATED)
unit = ForeignKeyField(Unit, backref="tasks")
campaign = ForeignKeyField(Campaign, backref="tasks")
Next code create units, campaign and tasks:
def fill_units(count):
units = []
with database.atomic():
for i in range(count):
units.append(Unit.create(name=f"unit{i}"))
return units
def init_campaign(count):
units = Unit.select().limit(count)
with database.atomic():
campaign = Campaign.create()
for unit in units:
Task.create(unit=unit, campaign=campaign)
return campaign
The problem appears when I'm trying to add more units into existing campaign. I need to select units which haven't been used in this campaign. In SQL I can do this using next query:
SELECT * FROM unit WHERE id NOT IN (SELECT unit_id FROM task WHERE campaign_id = 1) LIMIT 10
But how to do this using peewee?
The only way I've found yet is:
def get_new_units_for_campaign(campaign, count):
unit_names = [task.unit.name for task in campaign.tasks]
units = Unit.select().where(Unit.name.not_in(unit_names)).limit(count)
return units
It's somehow works but I'm 100% sure that it's the dumbest way to implement this. Could you show me the proper way to implement this?
Finally I found this:
Unit.select().where(Unit.id.not_in(campaign.tasks.select(Task.unit))).limit(10)
Which produces
SELECT "t1"."id", "t1"."name", "t1"."some_field", "t1"."created_at" FROM "unit" AS "t1" WHERE ("t1"."id" NOT IN (SELECT "t2"."unit_id" FROM "task" AS "t2" WHERE ("t2"."campaign_id" = 1))) LIMIT 10
Which matches with SQL query I've provided in my question.
P.S. I've done some research and it seems to be a proper implementation, but I'd appreciate if somebody correct me and show the better way (if exist).

peewee select where not working when using a model instance rather than a model directly

I have an instance of a peewee Model my_table = MyTable() from which I want to select some model instances.
I don't understand why this works:
In [0] [selection.p_name for selection in my_table.select() if selection.p_type == "Solar"]
Out [0] ['Solar, photovoltaic',
'Solar, photovoltaic',
'Solar, photovoltaic',
'Solar, photovoltaic',
'Solar, concentrated solar power',
'Solar, concentrated solar power']
but this doesn't:
In [1] selections = my_table.select().where(my_table.p_type=="Solar")
In [2] [t.p_name for t in selections]
Out [2]
Nothing is output. In fact, len(selections)=0
Am I doing something wrong?
My model definition is in one file and is as follows:
cafe3db = SqliteDatabase(db_fp)
class Cafe3BaseModel(Model):
class Meta:
database = cafe3db
class ScenarioTable(Cafe3BaseModel):
pathway_scenario_key = CharField(primary_key=True)
pathway_type = CharField()
pathway_name = CharField()
cafe3db.create_tables([ScenarioTable])
I then populate the tables. Here is a screen capture of the SQLite database as seen DB Browswer for SQLite:
I then create an instance of the table:
scenario_table = ScenarioTable()
Then, in a Python shell, I import the instance:
from x.y import scenario_table
I know it has all the model instances I expect (112):
>>> len(scenario_table.select())
112
And this works:
>>> [t.pathway_name for t in scenario_table.select() if t.pathway_type == 'Coal']
['Coal, sub-bituminous', 'Coal, bituminous', 'Coal, lignite', 'Coal, sub-bituminous', 'Coal, lignite', 'Coal, bituminous', 'Coal, bituminous', 'Coal, lignite']
But this doesn't:
>>> [t.pathway_name for t in scenario_table.select().where(scenario_table.pathway_type == 'Coal')]
[]
After trial and error, I was able to make things work by importing the model directly rather than an instance of the model.
So, rather than:
from x.y import scenario_table
I now have:
from x.y import ScenarioTable
and now:
selections = ScenarioTable.select().where(ScenarioTable.pathway_type=='Coal')
[t.pathway_name for t in selections]
Does return the expected list of model instance names.
So my question is now: why does the model instance select work, but not the model instance where?
I'm trying to reproduce your problem, but I can't.
First I create the database and the table:
import peewee
db = peewee.SqliteDatabase('test.db')
db.connect()
class my_table(peewee.Model):
p_name = peewee.CharField()
p_type = peewee.CharField()
class Meta:
database = db
db.create_tables([my_table])
Then I insert two rows:
d1 = my_table(p_name="Solar, photovoltaic", p_type="Solar")
d1.save()
d2 = my_table(p_name="Windmill", p_type="Wind")
d2.save()
After that I try your command:
>>> [t.p_name for t in my_table.select().where(my_table.p_type=="Solar")]
['Solar, photovoltaic']
So it works here - maybe you're doing something else wrong, but the code you provided is correct

Google App Engine relationtable and paging?

I do have the following datastore model:
class One(db.Model):
OneDateAdded = db.DateTimeProperty(auto_now_add=True)
OneTitle= db.StringProperty()
OneLink= db.LinkProperty()
class Two(db.Model):
TwoDateAdded = db.DateTimeProperty(auto_now_add=True)
TwoTitle= db.StringProperty()
TwoLink= db.LinkProperty()
class Three(db.Model):
ThreeDateAdded = db.DateTimeProperty(auto_now_add=True)
ThreeTitle= db.StringProperty()
ThreeisSomething = db.BooleanProperty(default=False)
ThreeLink= db.LinkProperty()
and a relation table:
class Relation(db.Model):
RelationDateAdded = db.DateTimeProperty(auto_now_add=True)
RelationOne = db.ReferenceProperty(One)
RelationTwo = db.ReferenceProperty(Two)
RelationThree = db.ReferenceProperty(Three)
when i tried to implement the PagedQuery Library i came arround the problem that i can´t use some sort of join due to GAE restrictions.
what i wan´t to accomplish is a query on my relationtable where RelationThree.ThreeisSomething = True
Looping over a set of results does not seem to be a solution because the paging would not work (gets 10 results 2 are true 8 are false resulting in a page with only 2 results ..)
is there a way to do something simple like this: (which does not work)
myPagedQuery = paging.PagedQuery(Release.all().filter('Three.ThreeisSomething =', True), 10)
You can use limit and offset documented here. Be wary though, that using them might be expensive - setting on offset of 100 and limit of 10 actually loads 110 records and gives you the last 10.

Error with dynamic classes and sqlalchemy

I am trying to write a logging system, which uses dynamic classes to make tables. Getting the classes created, and the tables created seems to be working fine, but trying to put entries into them is lead to an error message regarding mapping, below is the sample code and the error message.
Base = declarative_base()
#my init function
def tableinit(self,keyargs):
self.__dict__ = dict(keyargs)
#table creation
tableName = "newTable"
columnsDict["__tablename__"] = tableName
columnsDict["__init__"] = tableinit
columnsDict["id"] = Column("id",Integer, autoincrement = True, nullable = False, primary_key=True)
columnsDict["pid"] = Column("pid",Integer, ForeignKey('someparenttable.id')) #someparenttable is created with a hard coded class
newTable = type(tableName,(Base,),columnsDict)
tableClassDict[tableName]=newTable
#when doing an entry
newClassInst = subEntryClassDict[tableName]
newEntry = newClassInst(dataDict)
entryList.append(newEntry) # this is called in a for loop with the entries for someparenttable's entries also
self.session.add_all(entryList) # at this point the error occurs
The error:
UnmappedInstanceError: Class 'newTable' is mapped, but this instance lacks instrumentation. This occurs when the instance is created before sqlalchemy.orm.mapper(module.newTable) was called.
This is easier if you create a function to return a class that you set up normally. I've tried something like this and it works:
def getNewTable( db, table ):
class NewTable( Base ):
__tablename__ = table
__table_args__ = { 'schema': db }
id = Column( ...
return NewTable
newClassInst = getNewTable( 'somedb', 'sometable' )
newRow = newClassInst( data )
This problem is caused by lack of instruments function interfaces for the orm as the error description says. And it is actually caused by self.__dict__ = dict(keyargs) I think.
So this can be solved by reconstruct the init, which do not modify the injected functions by ORM.
Turn this
#my init function
def tableinit(self,keyargs):
self.__dict__ = dict(keyargs)
To
#my init function
def tableinit(self,**kwargs):
self.__dict__.update(kwargs)

Generic relation with sqlalchemy as in django contenttypes

I'm trying to make some generic apps using Sql Alchemy, such as tags or rating for any model. But I couldn't find any help in the docs. I really liked what I could do with the django contenttypes framework ? Is there any similar functionality in Sql Alchemy ?
I once wrote some example code about something similar to this (see http://taketwoprogramming.blogspot.com/2009/08/reusable-sqlalchemy-models.html).
The basic idea is that you can create a model like this:
#commentable
class Post(Base):
__tablename__ = 'posts'
id = sa.Column(sa.Integer, primary_key=True)
text = sa.Column(sa.String)
...where commentable is defined like this...
class BaseComment(object):
pass
def build_comment_model(clazz):
class_table_name = str(class_mapper(clazz).local_table)
metadata = clazz.metadata
comment_class_name = clazz.__name__ + 'Comment'
comment_class = type(comment_class_name, (BaseComment,), {})
comment_table_name = class_table_name + '_comments'
comment_table = sa.Table(comment_table_name, metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column(class_table_name + '_id',
sa.Integer,
sa.ForeignKey(class_table_name + '.id')),
sa.Column('text', sa.String),
sa.Column('name', sa.String(100)),
sa.Column('url', sa.String(255)),
)
mapper(comment_class, comment_table)
return comment_class, comment_table
def commentable(clazz):
comment_class, comment_table = build_comment_model(clazz)
clazz.Comment = comment_class
setattr(clazz, 'comments', relation(comment_class))
def add_comment(self, comment):
self.comments.append(comment)
setattr(clazz, 'add_comment', add_comment)
return clazz
Basically, the commentable decorator dynamically creates a new type and table, along with some helper methods to the decorated class. This is the test I used to test that the code works, which shows some example of how it would work...
class TestModels(SATestCase):
def test_make_comment(self):
p = Post()
p.text = 'SQLAlchemy is amazing!'
text = 'I agree!'
name = 'Mark'
url = 'http://www.sqlalchemy.org/'
c = Post.Comment()
c.text = text
c.name = name
c.url = url
p.add_comment(c)
Session.add(p)
# This is a method I use to force the reload of the objects from
# the database to make sure that when I test them, I'm actually
# pulling from the database rather than just getting the data
# of the object still in the session.
p = self.reload(p)
self.assertEquals(len(p.comments), 1)
c = p.comments[0]
self.assertEquals(c.text, text)
self.assertEquals(c.name, name)
self.assertEquals(c.url, url)
I wrote this awhile ago, but I don't think there's anything in SQLA that will do this kind of thing for you, but you can create something similar without too much trouble. In my example, I created new mapped classes and methods to use it on the fly in a class decorator.
I never really made much use out of it, but it might give you some ideas.

Categories