Well, there are other methods too ๐
Given the work I have been doing the past few months, I read a lot more code than I write. This article is about everyone's favourite Frappe Python API method: the get_doc
and patterns of using get_doc
I have seen most when reading custom app code. In this article, I want to discuss why probably you are over/mis-using get_doc
leading to inefficient Frappe code and what to do instead.
Observe the below code:
def get_airline_website(airplane_name):
airplane = frappe.get_doc("Airplane", airplane_name)
airline = frappe.get_doc("Airline", airplane.airline)
return airline.website_link
Now, go through the below Jinja code snippet:
I have copied this from a Print template in Production use.
So, you might ask what am I trying to point out here. In both the above code snippets, get_doc
is being used to get a value of just a field of the document.
get_doc
For This?When you use frappe.get_doc
, it fetches a lot of data for the document including all the fields (1 query) and child table rows (1 query for each child table).
So, basically when you do get_doc
for just getting the values of 1 or 2 fields, a lot of unnecessary data is being fetched. This becomes worse as the number of child table in a doctype increases. Now imagine get_doc
in a loop like it was in the jinja snippet above ๐ฑ
You are better off with using frappe.db.get_value
in case you just want values of a few fields of a particular document:
customer_name = frappe.db.get_value("Sales Invoice", si_name, "customer_name")
The above line will only fetch the customer_name
field using a single database call. You can even get multiple fields using this method by passing a list of field names:
airline_details = frappe.db.get_value(
"Airline", "IndiGo",
["name", "website_link"],
as_dict=True
)
# {"name": "IndiGo", "website_link": "https://goindigo.com"}
If you don't pass as_dict
as True
, this method will return a tuple instead of a dict.
If you want to get child table rows without using get_doc
and in a single database call, you need to know how a child table is linked to its parent. Consider this Ride document:
It has a table field called items
and is linked to Ride Trip Item
DocType. If we open up mariadb console and peek into the Ride Trip Item
table:
As you can see, it has a few fields which link it to its parent, specially the parent
field which is the name of the parent document. Now, we can use this information to get child table rows for a given document using get_all
method:
items = frappe.get_all(
"Ride Trip Item",
fields=["source", "destination"],
filters={
"parent": "RIDE-09-2023-0003", "parenttype": "Ride"
},
order_by="idx"
)
# [{'source': 'Airport', 'destination': 'Mall'},
# {'source': 'Office', 'destination': 'Airport'}]
You can even omit the parenttype
filter if you know this child table is only used with one DocType. Notice the order_by="idx"
part, this will return the rows in the order in which the items are in the child table.
Again, observe the below code snippet:
Basically, the developer of the above custom app is setting a field to 1
based on some condition. The above code uses get_doc
to get the full document, uses nothing that the get_doc
fetched, sets the value and calls db_update
to update it in database. This all could have been done in a single line using set_value
like this:
frappe.db.set_value("Sales Invoice", i.sales_invoice, "gate_pass", 1)
Look how clean the code becomes! And efficient too (single database call)!
get_doc
Is InevitableOne of the inevitable use case of get_doc
is to call a DocType method:
class Employee(Document):
def get_ytd_salary(self):
...
emp = frappe.get_doc("Employee", "EMP-001")
print(emp.get_ytd_salary())
Keep in mind that db.get_value
/db.set_value
methods don't apply permissions while doc.save()
method does. If you want user permissions to be applied, then you have to use get_doc
.
One more thing to note here is that, db.set_value
method will by pass any validations that you might have in your controller code. If you want some validations to be run for your DocType, then you will need to do get_doc
or get_cached_doc
(see next section), set the value on the object and call save
(which runs the validations).
get_cached_doc
Even when get_doc
is inevitable, you can many times use get_cached_doc
, which is similar to get_doc
but will look up the cache before hitting the database. It will cache it after the first call. This can be quite handy if you are using a document in multiple functions during a request.
For instance, in this particular PR on ERPNext repository, Rohit just replaced a get_doc
with get_cached_doc
and got ~45% faster API.
One of the reasons of get_doc
being overused in my opinion is that it is the first Python API method covered in tutorials and developers sometimes look no further since get_doc
can do everything. But knowing about other ORM methods like get_value
, set_value
, get_all
etc. can help you write much more efficient and clean code.
Until next time โ๐ผ
Hussain is the host of #BuildWithHussain on YouTube and a training expert at Frappe School. He has a passion for teaching and building stuff!