PonyORM retrieve object from class Entity problem - python

Let's say I have these two classes:
class TeamMember(db.Entity):
member_id= PrimaryKey(int, auto=True)
name = Required(str)
team = Required('Team')
class Team(db.Entity):
team_id= PrimaryKey(int, auto=True)
name = Required(str)
team_members = Set(TeamMember)
I want to select all TeamMembers that are in specific team (ex. team_id==1). Query would look something like this (C1):
TeamMember.select(lambda member: member.team == 1)[:]
If I write it like that, I'm getting error below:
Incomparable types 'Team' and 'int' in expression: member.team == 1
On the other hand, I can write this and it will work (C2):
TeamMember.select(lambda member: member.team == Team[1])[:]
But, I don't wan't to write it like it, because I want to create generic function that will work for every Entity class:
def get_instances_from_db(classname, classname_var, var_value):
"""
:param classname: name of class
:param classname_var: name of class variable to search by
:param var_value: value of class variable
:return:
"""
return classname.select(lambda v: getattr(v, classname_var) == var_value)[:]
Above method will work for variable that's isn't relating to other class Entity like:
members = get_instances_from_db(TeamMember, "name", "some_team_member_name")
Finally, my question is: Is it possible to set query to search by integer, and not by Entity object. Or, is there way to use line 'C1'?
Hope I'm clear enough! :)

Related

Pytest - how to stub member variable

I apologise - this is my first attempt at using pytest or any python testing library, but I have done a small amount of JUnit so am vaguely familiar with the principles.
Basically, the class I want to test has a couple of member variables that I want to stub. Specifically, I only need some customer details. I access this under the OrderController class in its class variable 'orders' (a list of dictionaries with purchase id as key and order objects as values). When I get this order object I would like to access the customer attribute which is comprised of their name and address - this address attribute is another member variable.
Below is the address_label.py module (I'm sorry about the comments - it is for University)
"""
--------------------------------------------------------------------------------------------
title : address_label.py
description : Formats order data for creation of address labels in the pdf.py module.
python_version : 3.7.9
--------------------------------------------------------------------------------------------
"""
from .order_controller import OrderController
from .pdf import Pdf
class AddressLabel:
"""
A class for formatting data to be input into address label pdf.
...
Attributes
----------
order_controller : OrderController
member variable to access order instances
pdf : Pdf
member variable to invoke writing of pdf
Methods
-------
create_address_label(orders_selected):
Create strings to be output in pdf
"""
def __init__(self):
"""
Constructs all the necessary attributes for the AddressLabel object.
"""
self._order_controller = OrderController(None)
self._pdf = Pdf()
def create_address_label(self, orders_selected):
"""
For each of the orders selected with checkboxes will find the data for that order
and format is suitable for the pdf module.
Parameters:
orders_selected: an array of the row data from each row checked with a checkbox
(each item is a string).
"""
for index, order in enumerate(orders_selected):
order_id = int(order[0]) - 1
order_obj = self._order_controller.orders['order_' + order[0]]
address = [order_obj.customer.first_name + ' ' + order_obj.customer.last_name,
order_obj.customer.address.line_one, order_obj.customer.address.city]
self._pdf.write_address_label(address, order_id, index)
return address, order_id, index
This is what I have so far for test_address_label.py, but I notice that it is still contacting the main OrderController class and therefore failing - how can I stop this?
import pytest
from main.business_logic.address_label import AddressLabel
class Address:
def __init__(self, line_one, line_two, city):
self.line_one = line_one
self.line_two = line_two
self.city = city
class Customer:
def __init__(self, address, first_name, last_name):
self.address = address
self.first_name = first_name
self.last_name = last_name
class Order:
def __init__(self, customer):
self.customer = customer
class OrderController:
orders = {
'order_1': Order(customer=setup_customer())
}
def __init__(self, x):
pass
#staticmethod
def setup_customer():
def setup_address():
return Address(line_one='Test Line One',
line_two='Test Line Two', city='Test City')
address = setup_address()
return Customer(address=address, first_name='Test First Name', last_name='Test Last Name')
#pytest.fixture
def _order_controller():
return OrderController()
def test_address_label(_order_controller):
address_label = AddressLabel()
orders_selected = [['1', 'Test Name', '2021-03-12', 'Status', '£200']]
scenario_one_address = ['Test First Name Test Last Name', 'Test Line One', 'Test City']
address_label_contents = address_label.create_address_label(
orders_selected)
assert address_label_contents == (scenario_one_address, 1, 0)
In any case, if anyone had any good resources to learn this from that'd be great - I've read a lot of tutorials but they all use such elementary examples that don't apply to a lot of my use cases...
Thank you in advance!

Accessing elements of lists, and calling their fuctions

Here is Customer class:
class Customer:
def __init__(self, timestamp, cid, item_count):
self.time_stamp = timestamp
self.customer_name = cid
self.item_count = item_count
def checkout(self, new_timestamp):
self.time_stamp = new_timestamp
def get_cus_name(self):
return self.customer_name
If I create an empty list of Customer objects like:
customers = [Customer]
And then somewhere else I try to call Customer methods in a loop like:
def checkout_customer(self, cid):
for cus in self.customers:
if cus.get_cus_name == cid:
cus.checkout(self.cur_num_customers + 7)
why do I get an error when I try to call cus.checkout? My ide tells me that it expects a Customer but got an int. Why doesn't it pass itself into the 'self' arg here?
However if I just create a Customer object and directly call its methods, it works fine:
def foo(self):
cus = Customer(1,'pop',2)
cus.checkout(23)
This is my first time learning python, and ive been stuck trying to figure out lists, and accessing its members. Perhaps my initialization of self.custormers = [Customer] is incorrect?
EDIT:
In my constructor of tester class I create an empty list like this:
self.customer = [Customer]
I am able to add customers no problem:
def add_custormer(self, customer):
self.customers.append(customer)
My problem is not adding customers, but accessing their methods once they are in a list. Doing something like this self.customers[0].checkout(1,'pop',2) gives me an error "Expected type 'Customer' got int".
I am not sure of the class where checkout_customer lives but I am assuming you declare the list self.customers somewhere in it.
self.costumers = []
If you intend to add an element Customer to the list you should use something like: self.customers.append(Customer(x,y,z)) since you want to add a new customer to the list and when doing so you are required to initialize the Customer class.
I didn't try the code but I believe something like this should work:
def foo(self):
self.customers.append(Customer(1,'pop',2))
self.checkout_customers(23)

how to get class instances out of a dict in python

I have a class `Collection' that looks like this:
class Collection():
def __init__(self, db, collection_name):
self.db = db
self.collection_name = collection_name
if not hasattr(self.__class__, 'client'):
self.__class__.client = MongoClient()
self.data_base = getattr(self.client, self.db)
self.collection = getattr(self.data_base, self.collection_name)
def getCollectionKeys(self):
....etc.
I cleverly created a function to create a dictionary of class instances as follows:
def getCollections():
collections_dict = {}
for i in range(len(db_collection_names)):
collections_dict[db_collection_names[i]] = Collection(database_name, db_collection_names[i])
return collections_dict
it works. however, whenever I want to access a class instance, I have to go through the dictionary:
agents_keys = collections_dict['agents'].getCollectionKeys()
I would love to just write:
agents_keys = agents.getCollectionKeys()
Is there a simple way to get those instances "out" of the dict?
You can get a reference to items in a vanilla python dictionary using a generator object in a for loop, or by using a list expression.
agent_keys = [x.getCollectionKeys() for x in collections_dict.values()]
or this
agent_keys = []
for name in db_collection_names:
#do something with individual item
#there could also be some logic here about which keys to append
agent_keys.append(collections_dict[name].getCollectionKeys())
#now agent_keys is full of all the keys
My mental model of how objects are interacted with in python. Feel free to edit if you actually know how it works.
You cannot "take" items of the dictionary per say unless you call the del operator which removes the association of a variable name (that is what you type in the editor like "foo" and "bar") with an object ( the actual collections of bits in the program your machine sees). What you can do is get a reference to the object, which in python is a symbol that for all your intents and purposes is the object you want.
The dictionary just holds a bunch of references to your database objects.
The expression collections_dict['agents'] is equivalent to your original database object that you put into the dictionary like this
collections_dict['agents'] = my_particular_object

sqlalchemy access parent class attribute

Looking at the bottom of the post you can see i have three classes. The code here is pseudo code written on the fly and untested however it adequately shows my problem. If we need the actual classes I can update this question tomorrow when at work. So ignore syntax issues and code that only represents a thought rather than the actual "code" that would do what i describe there.
Question 1
If you look at the Item search class method you can see that when the user does a search i call search on the base class then based on that result return the correct class/object. This works but seems kludgy. Is there a better way to do this?
Question 2
If you look at the KitItem class you can see that I am overriding the list price. If the flag calc_list is set to true then I sum the list price of the components and return that as the list price for the kit. If its not marked as true I want to return the "base" list price. However as far as I know there is no way to access a parent attribute since in a normal setup it would be meaningless but with sqlalchemy and shared table inheritance it could be useful.
TIA
class Item(DeclarativeBase):
__tablename__ = 'items'
item_id = Column(Integer,primary_key=True,autoincrement=True)
sku = Column(Unicode(50),nullable=False,unique=True)
list_price = Column(Float)
cost_price = Column(Float)
item_type = Column(Unicode(1))
__mapper_args__ = {'polymorphic_on': item_type}
__
def __init__(self,sku,list_price,cost_price):
self.sku = sku
self.list_price = list_price
self.cost_price = cost_price
#classmethod
def search(cls):
"""
" search based on sku, description, long description
" return item as proper class
"""
item = DBSession.query(cls).filter(...) #do search stuff here
if item.item_type == 'K': #Better way to do this???
return DBSession.query(KitItem).get(item.item_id)
class KitItem(Item):
__mapper_args__ = {'polymorphic_identity': 'K'}
calc_list = Column(Boolean,nullable=False,default=False)
#property
def list_price(self):
if self.calc_list:
list_price = 0.0
for comp in self.components:
list_price += comp.component.list_price * comp.qty
return list_price
else:
#need help here
item = DBSession.query(Item).get(self.item_id)
return item.list_price
class KitComponent(DeclarativeBase):
__tablename__ = "kit_components"
kit_id = Column(Integer,ForeignKey('items.item_id'),primarykey=True)
component_id = Column(Integer,ForeignKey('items.item_id'),primarykey=True)
qty = Column(Integer,nullable=False, default=1)
kit = relation(KitItem,backref=backref("components"))
component = relation(Item)
Answer-1: in fact you do not need to do anything special here: given that you configured your inheritance hierarchy properly, your query will already return proper class for every row (Item or KitItem). This is the advantage of the ORM part. What you could do though is to configure the query to immediatelly load also the additional columns which do belong to children of Item (from your code this is only calc_list column), which you can do by specifying with_polymorphic('*'):
#classmethod
def search(cls):
item = DBSession.query(cls).with_polymorphic('*').filter(...) #do search stuff here
return item
Read more on this in Basic Control of Which Tables are Queried.
To see the difference, enabled SQL logging, and compare your tests scripts with and without with_polymorphic(...) - you will most probably require less SQL statements being executed.
Answer-2: I would not override one entry attributed with one which is purely computed. Instead I would just create another computed attribute (lets call it final_price), which would look like following for each of two classes:
class Item(Base):
...
#property
def total_price(self):
return self.list_price
class KitItem(Item):
...
#property
def total_price(self):
if self.calc_list:
_price = 0.0
for comp in self.components:
_price += comp.component.list_price * comp.qty
return _price
else:
# #note: again, you do not need to perform any query here at all, as *self* is that you need
return self.list_price
Also in this case, you might think of configuring the relationship KitItem.components to be eagerly loaded, so that the calculation of the total_price will not trigger additional SQL. But you have to decide yourself if this is beneficial for your use cases (again, analyse the SQLs generated in your scenario).

Defining circular references using zope.schema

I'm trying to do the following, define two classes whose instances mutually reference one another, like Users and Groups in the following exemple. A User can belong to several groups and a Group can contains several users. The actual data is stored in a database and there it is a simple matter of many-to-many relationship using foreign keys. No problem at all.
Afterward the data is loaded through an ORM and stored in instances of python objects. Still no problem at all as the ORM used (SQLAlchemy) manage backrefs.
Now I want to check that the python objects comply to some interface using zope.interface and zope.schema. That's where I get into troubles.
import zope.schema as schema
from zope.interface import Interface, implements
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
# user_list = schema.List(title = u"List of Users in this group", value_type = sz.Object(IUser))
class IUser(Interface):
name = schema.TextLine(title=u"User's name")
group_list = schema.List(title = u"List of Groups containing that user",
value_type = schema.Object(IGroup))
IGroup._InterfaceClass__attrs['user_list'] = zs.List(title = u"List of Users in this group", required = False, value_type = zs.Object(IUser))
class Group(object):
implements(IGroup)
def __init__(self, name):
self.name = name
self.user_list = []
class User(object):
implements(IUser)
def __init__(self, name):
self.name = name
self.group_list = []
alice = User(u'Alice')
bob = User(u'Bob')
chuck = User(u'Chuck')
group_users = Group(u"Users")
group_auditors = Group(u"Auditors")
group_administrators = Group(u"Administrators")
def add_user_in_group(user, group):
user.group_list.append(group)
group.user_list.append(user)
add_user_in_group(alice, group_users)
add_user_in_group(bob, group_users)
add_user_in_group(chuck, group_users)
add_user_in_group(chuck, group_auditors)
add_user_in_group(chuck, group_administrators)
for x in [alice, bob, chuck]:
errors = schema.getValidationErrors(IUser, x)
if errors: print errors
print "User ", x.name, " is in groups ", [y.name for y in x.group_list]
for x in [group_users, group_auditors, group_administrators]:
errors = schema.getValidationErrors(IGroup, x)
if errors: print errors
print "Group ", x.name, " contains users ", [y.name for y in x.user_list]
My problem is the commented line. I can't define IGroup using IUser because at that time IUser is not yet defined. I've found a workaround completing the definition of IGroup after the definition of IUser but that is not satisfying at all, because IUser and IGroup are defined in different source files and part of IGroup is defined in the file defining IUser.
Is there any proper way to do that using zope.schema ?
Modify the field after definition:
#imports elided
class IFoo(Interface):
bar = schema.Object(schema=Interface)
class IBar(Interface):
foo = schema.Object(schema=IFoo)
IFoo['bar'].schema = IBar
Martijn's answer seems a bit more graceful and self-documenting, but this is a bit more succinct. Neither is perfect (compared to say, Django's solution of using string names for foreign keys) -- pick your poison.
IMHO, it would be nice to specify a dotted name to an interface instead of an identifier. You could pretty easily create a subclass of schema.Object to this end for your own use, should you find that approach useful.
You could define a base, or abstract, interface for IUser:
class IAbstractUser(Interface):
name = schema.TextLine(title=u"User's name")
class IGroup(Interface):
name = schema.TextLine(title=u"Group's name")
user_list = schema.List(
title=u"List of Users in this group",
value_type=schema.Object(IAbstractUser))
class IUser(IAbstractUser):
group_list = schema.List(
title=u"List of Groups containing that user",
value_type=schema.Object(IGroup))
Because IUser is a subclass of IAbstractUser, objects implementing the former also satisfy the latter interface.
Edit: You can always still apply sdupton's dynamic after-the-fact alteration of the IGroup interface after you defined IUser:
IGroup['user_list'].value_type.schema = IUser
I'd still use the Abstract interface pattern to facilitate better code documentation.

Categories