I have a list of classes stored in memory that I am trying to parse through various types. It is referenced through the method get_inventory().
When I call the classes individually, they resolve as I would expect.
But when I try to nest one in the other, the value is returning null.
The code, followed by some examples:
class Account(graphene.ObjectType):
account_name = graphene.String()
account_id = graphene.String()
def resolve_account(
self, info,
account_id=None,
account_name=None
):
inventory = get_inventory()
result = [Account(
account_id=i.account_id,
account_name=i.account_name
) for i in inventory if (
(i.account_id == account_id) or
(i.account_name == account_name)
)]
if len(result):
return result[0]
else:
return Account()
account = graphene.Field(
Account,
resolver=Account.resolve_account,
account_name=graphene.String(default_value=None),
account_id=graphene.String(default_value=None)
)
class Item(graphene.ObjectType):
item_name = graphene.String()
region = graphene.String()
account = account
def resolve_item(
self, info,
item_name=None
):
inventory = get_inventory()
result = [Item(
item_name=i.item_name,
region=i.region,
account=Account(
account_id=i.account_id
)
) for i in inventory if (
(i.item_name == item_name)
)]
if len(result):
return result[0]
else:
return Item()
item = graphene.Field(
Item,
resolver=Item.resolve_item,
item_name=graphene.String(default_value=None)
)
class Query(graphene.ObjectType):
account = account
item = item
schema = graphene.Schema(query=Query)
Let's assume I have an account foo that has an item bar. The below queries return the fields correctly.
{
account(accountName:"foo") {
accountName
accountId
}
}
{
item(itemName: "bar") {
itemName
region
}
}
So if I wanted to find the account that has the item bar, I would think I could query bar and get foo. But it returns the account fields as null.
{
item(itemName: "bar") {
itemName
region
account {
accountId
accountName
}
}
}
Recall that as part of resolve_item, I am doing account=Account(account_id=i.account_id) - I would expect this to work.
If I alter the last return statement of resolve_account to the below, accountId always returns yo.
...
else:
return Account(
account_id='yo'
)
So this tells me that my resolver is firing, but the invocation in resolve_item is not passing account_id properly.
What am I doing wrong?
This is because the arguments on a field are only available to its immediate children. If you need to retrieve the argument in nested elements, your top level resolver needs to return the argument as part of the source and your nested element can access it arguments now from the source object.
From what I am seeing, it looks like rather than being passed in as an argument, account_id is being stored already as part of account in the ObjectType.
So if I add the below, I can get to the result that I want.
account_id = account_id if getattr(self, "account", None) is None else self.account.account_id
Related
I am trying to use setFilter method in a custom class extended from QSqlTableModel class. I used the same method before for other classes and it worked well but this time whatever filter I apply, I get 0 results. Even when I do:
self.setFilter("")
I get 0 results. However, for other classes I used to use this line to reset existing filters meaning I'm supposed to return all the objects in the table as a result.
Note: If i dont use any filters, I can retrieve all the objects properly.
Here is my code:
I retrieve the data from a csv file:
file = open(fileName, 'r', encoding="utf-8")
csvreader = csv.reader(file)
rows = []
for row in csvreader:
rows.append(row)
file.close()
return rows
Creation of the table:
createTableQ= QSqlQuery()
createTableQ.exec_(
"""
CREATE TABLE cities (
id INTEGER PRIMARY KEY UNIQUE NOT NULL,
name VARCHAR(100) NOT NULL,
countryId INTEGER,
countryCode VARCHAR(100) NOT NULL,
countryName VARCHAR(100) NOT NULL
)
"""
)
createTableQ.finish()
Then apply a few preprocessing on the data then insert them in the sqlite file:
cities=convertToValue(cities)
for city in cities:
insertDataQ=QSqlQuery()
if not insertDataQ.exec_(city):
print(insertDataQ.lastError().text())
insertDataQ.finish()
convertToValue method takes the data and formats it in a SQL insert query format. That function is not the problem I use it in another class where I can use filter.
The above methods work only once to create the sqlite file anyway.
My class (NOTE: setCityData method is for the sqlite file creation i explained above, runs only once if the file does not exist):
class CityListModel(QSqlTableModel):
def __init__(self, parent=None):
super(CityListModel, self).__init__(parent)
setcityData()
self.setTable(table_name)
self.setSort(1, Qt.AscendingOrder)
self.setEditStrategy(QSqlTableModel.OnFieldChange)
self.recipient = ""
self.select()
while self.canFetchMore():
self.fetchMore()
def data(self, index, role):
if role < Qt.UserRole:
return QSqlTableModel.data(self, index, role)
sql_record = QSqlRecord()
sql_record = self.record(index.row())
return sql_record.value(role - Qt.UserRole)
def roleNames(self):
"""Converts dict to hash because that's the result expected
by QSqlTableModel"""
names = {}
id = "id".encode()
name = "name".encode()
countryId = "countryId".encode()
countryCode = "countryCode".encode()
countryName = "countryName".encode()
names[hash(Qt.UserRole + 0)] = id
names[hash(Qt.UserRole + 1)] = name
names[hash(Qt.UserRole + 2)] = countryId
names[hash(Qt.UserRole + 3)] = countryCode
names[hash(Qt.UserRole + 4)] = countryName
return names
My filter method which returns 0 results with any filter text:
#Slot(str)
def applyFilter(self, filterCountryName):
#self.setFilter("countryName = 'Turkey'")
self.setFilter("")
Edit: I call the function from qml side the same way I do for my other custom classes. Where I call it is irrelevant but basically this will be the end product:
CustomSelector{
id:countryList
Layout.fillHeight: true
Layout.fillWidth: true
isLocked: false
model:countryListModel
shownItemCount:4
selectedItem.onTextChanged: {
cityListModel.applyFilter(countryList.selectedItem.text)
}
}
While trying to give a reproducable example I realized the connection to my sqlite database gets broken somewhere before trying to set the filter. Therefore, setFilter can never succeed.
I wish to open a GraphQL mutation endpoint where an id and string is sent in. This would then gather the item based on the ID and change a value in the object, and save the change in the item in the DB.
It would look something like this:
Query SomeMutation{
ExampleMutation(input: {id: 1, Status: "Something"}){
ExampleObject{
id
Status
}
}
}
I currently have the following setup:
(Schema.py)
class Mutation(graphene.ObjectType):
ExampleMutation = ExampleMutation.Field()
(Schema_ExampleObject.py)
class Captcha(SQLAlchemyObjectType):
class Meta:
model = ExampleObjectModel
interfaces = (relay.Node,)
(ExampleMutation.py)
class Attributes:
id = graphene.Int(description="Id")
status = graphene.String(description="Status")
class ExampleMutationInput(graphene.InputObjectType, Attributes):
pass
class ExampleSolution(graphene.Mutation):
ExampleObject= graphene.Field(lambda: ExampleObject, description="Example Object")
class Arguments:
input = ExampleMutationInput(required=True)
def mutate(self, info, input):
data = input
# **** Here I want to query an item from the DB based on the ID and change a value in it, then save it in the DB and return the new object to the GraphQL. ****
return ExampleMutation(exampleobject=exampleobject)
I looked up at solutions online and I saw library calls that would work in the following manner:
item = ExampleObject.query.filter(blablanla)
But the Object doesn't have such functions as "Query" so I'm left confused.
I have found the correct way of performing the operation:
query = ExameObject.get_query(info=info)
query = query.filter(ExampleObject.id == data.id)
example_object = query.first()
EDIT: This is very similiar to SqlAlchemy - Filtering by Relationship Attribute in that we are both trying to filter on relationship attributes. However, they are filtering on matching an exact value, whereas I am filtering by using like/contains. Because of this, as pointed out in the comments, my solution requires an extra step that was not apparent in the other post.
Let me preface this with: I'm still quite new to SQLAlchemy, so it's entirely possible I'm going about this in the exact wrong way.
I have a Flask API that has defined "alerts" and "events". An alert can only belong to a single event, and an event can have multiple alerts. The schema for the alerts is as follows:
class Alert(PaginatedAPIMixin, db.Model):
__tablename__ = 'alert'
id = db.Column(db.Integer, primary_key=True, nullable=False)
type = db.relationship('AlertType')
type_id = db.Column(db.Integer, db.ForeignKey('alert_type.id'), nullable=False)
url = db.Column(db.String(512), unique=True, nullable=False)
event_id = db.Column(db.Integer, db.ForeignKey('event.id'), nullable=False)
event = db.relationship('Event')
def __str__(self):
return str(self.url)
def to_dict(self):
return {'id': self.id,
'event': self.event.name,
'type': self.type.value,
'url': self.url}
One of the API endpoints lets me get a list of alerts based on various filter criteria. For example, get all alerts of alert_type X, or get all alerts with X inside their alert_url. However, the one that is stumping me is that I want to be able to get all alerts with X inside their associated event name.
Here is the API endpoint function (the commented out events bit is my initial "naive" approach that does not work due to event being a relationship), but you can get the idea what I'm trying to do with the filtering.
def read_alerts():
""" Gets a list of all the alerts. """
filters = set()
# Event filter
#if 'event' in request.args:
# filters.add(Alert.event.name.like('%{}%'.format(request.args.get('event'))))
# URL filter
if 'url' in request.args:
filters.add(Alert.url.like('%{}%'.format(request.args.get('url'))))
# Type filter
if 'type' in request.args:
type_ = AlertType.query.filter_by(value=request.args.get('type')).first()
if type_:
type_id = type_.id
else:
type_id = -1
filters.add(Alert.type_id == type_id)
data = Alert.to_collection_dict(Alert.query.filter(*filters), 'api.read_alerts', **request.args)
return jsonify(data)
The filters set that is built gets fed to the to_collection_dict() function, which essentially returns a paginated list of the query with all of the filters.
def to_collection_dict(query, endpoint, **kwargs):
""" Returns a paginated dictionary of a query. """
# Create a copy of the request arguments so that we can modify them.
args = kwargs.copy()
# Read the page and per_page values or use the defaults.
page = int(args.get('page', 1))
per_page = min(int(args.get('per_page', 10)), 100)
# Now that we have the page and per_page values, remove them
# from the arguments so that the url_for function does not
# receive duplicates of them.
try:
del args['page']
except KeyError:
pass
try:
del args['per_page']
except KeyError:
pass
# Paginate the query.
resources = query.paginate(page, per_page, False)
# Generate the response dictionary.
data = {
'items': [item.to_dict() for item in resources.items],
'_meta': {
'page': page,
'per_page': per_page,
'total_pages': resources.pages,
'total_items': resources.total
},
'_links': {
'self': url_for(endpoint, page=page, per_page=per_page, **args),
'next': url_for(endpoint, page=page + 1, per_page=per_page, **args) if resources.has_next else None,
'prev': url_for(endpoint, page=page - 1, per_page=per_page, **args) if resources.has_prev else None
}
}
return data
I understand that I can get the filtered list of alerts by their associated event name doing something along these lines with options and contains_eager:
alerts = db.session.query(Alert).join(Alert.event).options(contains_eager(Alert.event)).filter(Event.name.like('%{}%'.format(request.args.get('event')))).all()
But I have not gotten something similar to that to work when added to the filters set.
i am using mongoengine to integrate with flask , i wanted to know how to get document object id every time i try i get File "/var/www/flask_projects/iot_mongo/app.py", line 244, in post return jsonify(user.id) AttributeError: 'BaseQuerySet' object has no attribute 'id'
class Test(Resource):
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('email',required=True, help='email')
args=parser.parse_args()
user=AdminUser.objects(email=args['email'])
return jsonify(user.id)
api.add_resource(Test,'/test')
if __name__ == '__main__':
app.run(debug=True)
I've been doing this. An example User model would be like~
class User(Document):
first_name = StringField(required=True, max_length=50)
last_name = StringField(required=True, max_length=50)
username = StringField(required=True)
password = StringField(required=True, min_length=6)
def to_json(self):
return {
"_id": str(self.pk),
"first_name": self.first_name,
"last_name": self.last_name,
"username": self.username,
"password": self.password
}
I convert the id to a string. I would then get a single object with~
user = User.objects.get(pk=user_id)
return user.to_json()
for a whole object, but if I just want the id I would do...
user.pk()
I created my own to_json method that converts the primary key to a string because otherwise it would return "id": ObjectID("SomeID") instead of neatly printing "id": "SomeID".
Hope this helps!
If you want to find someone by email I suggest~
User.objects.get(email=args['email'])
Check out the documentation, Document.objects is a QuerySet object.
You seem to be expecting that this part of your code
user=AdminUser.objects(email=args['email']) # user will be a QuerySet
will give you a single result, which is not the case, it will give you a QuerySet with zero or more results. It does not have an attribute id, this is why you get the error message you are seeing when you try to access this attribute here:
return jsonify(user.id) # QuerySet does not have the attribute id
You need to fetch the actual result(s) you want from it, assuming you are sure your query will return a single result, or do not care that there might be more than one result and just want the first one, you probably want something along these lines:
user=AdminUser.objects(email=args['email']).first() # extract first result
return jsonfiy(user)
Alernatively, returning all results would look like this:
users=AdminUser.objects(email=args['email']).all() # extract all results
return jsonfiy(users)
I'd like to know whether it is possible to get the "original id" of an object as the result of the query. Whenever I make a request to the server, it returns the node "global identifier", something like U29saWNpdGFjYW9UeXBlOjEzNTkxOA== .
The query is similar to this one:
{
allPatients(active: true) {
edges {
cursor
node {
id
state
name
}
}
}
and the return is:
{
"data": {
"edges": [
{
"cursor": "YXJyYXljb25uZWN0aW9uOjA=",
"node": {
"id": "U29saWNpdGFjYW9UeXBlOjEzNTkxOA==",
"state": "ARI",
"name": "Brad"
}
}
]
}
}
How can I get the "original" id of the object at the database level (e.g. '112') instead of that node unique identifier?
ps.: I am using graphene-python and Relay on the server side.
Overriding default to_global_id method in Node object worked out for me:
class CustomNode(graphene.Node):
class Meta:
name = 'Node'
#staticmethod
def to_global_id(type, id):
return id
class ExampleType(DjangoObjectType):
class Meta:
model = Example
interfaces = (CustomNode,)
First option, remove relay.Node as interface of your objectNode declaration.
Second option, use custom resolve_id fonction to return id original value.
Example
class objectNode(djangoObjectType):
.... Meta ....
id = graphene.Int(source="id")
def resolve_id("commons args ...."):
return self.id
Hope it helps
To expand on the top answer and for those using SQLAlchemy Object Types, this worked for me:
class CustomNode(graphene.Node):
class Meta:
name = 'myNode'
#staticmethod
def to_global_id(type, id):
return id
class ExampleType(SQLAlchemyObjectType):
class Meta:
model = Example
interfaces = (CustomNode, )
If you have other ObjectTypes using relay.Node as the interface, you will need to use a unique name under your CustomNode. Otherwise you will get and assertion error.
With this you can retrive the real id in database:
def get_real_id(node_id: str):
_, product_id_real = relay.Node.from_global_id(global_id=node_id)
return product_id_real