refactor!: better API contracts for virtual doctype

Current APIs implement class methods as instance method, which is
problamamtic while implementing methods. E.g. If load_from_db doesn't
like empty docname then all class method will stop working.

This change while breaking is essential for usability of virtual
doctype.
This commit is contained in:
Ankush Menat 2022-07-22 13:03:29 +05:30
parent b8d56eaefb
commit e8efd64dbc
4 changed files with 67 additions and 22 deletions

View file

@ -8,7 +8,7 @@ from frappe.model.document import Document
class test(Document):
def db_insert(self):
def db_insert(self, *args, **kwargs):
d = self.get_valid_dict(convert_dates_to_str=True)
with open("data_file.json", "w+") as read_file:
json.dump(d, read_file)
@ -18,26 +18,22 @@ class test(Document):
d = json.load(read_file)
super(Document, self).__init__(d)
def db_update(self):
def db_update(self, *args, **kwargs):
d = self.get_valid_dict(convert_dates_to_str=True)
with open("data_file.json", "w+") as read_file:
json.dump(d, read_file)
def get_list(self, args):
@staticmethod
def get_list(args):
with open("data_file.json") as read_file:
return [frappe._dict(json.load(read_file))]
def get_value(self, fields, filters, **kwargs):
# return []
with open("data_file.json") as read_file:
return [json.load(read_file)]
@staticmethod
def get_count(args):
return 5
def get_count(self, args):
# return []
with open("data_file.json") as read_file:
return [json.load(read_file)]
def get_stats(self, args):
@staticmethod
def get_stats(args):
# return []
with open("data_file.json") as read_file:
return [json.load(read_file)]

View file

@ -24,7 +24,7 @@ def get():
# If virtual doctype get data from controller het_list method
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
data = compress(controller(args.doctype).get_list(args))
data = compress(controller.get_list(args))
else:
data = compress(execute(**args), args=args)
return data
@ -37,7 +37,7 @@ def get_list():
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
data = controller(args.doctype).get_list(args)
data = controller.get_list(args)
else:
# uncompressed (refactored from frappe.model.db_query.get_list)
data = execute(**args)
@ -52,7 +52,7 @@ def get_count():
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
data = controller(args.doctype).get_count(args)
data = controller.get_count(args)
else:
distinct = "distinct " if args.distinct == "true" else ""
args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
@ -528,7 +528,7 @@ def get_sidebar_stats(stats, doctype, filters=None):
if is_virtual_doctype(doctype):
controller = get_controller(doctype)
args = {"stats": stats, "filters": filters}
data = controller(doctype).get_stats(args)
data = controller.get_stats(args)
else:
data = get_stats(stats, doctype, filters)

View file

@ -0,0 +1,46 @@
from typing import Protocol
class VirtualDoctype(Protocol):
"""This class documents requirements that must be met by a doctype controller to function as virtual doctype
Additional requirements:
- DocType controller has to inherit from `frappe.model.document.Document` class
Note:
- "Backend" here means any storage service, it can be a database, flat file or network call to API.
"""
# ============ class/static methods ============
@staticmethod
def get_list(args):
"""Similar to reportview.get_list"""
...
@staticmethod
def get_count(args) -> int:
"""Similar to reportview.get_count, return total count of documents on listview."""
...
@staticmethod
def get_stats(args):
"""Similar to reportview.get_stats, return sidebar stats."""
...
# ============ instance methods ============
def db_insert(self, *args, **kwargs) -> None:
"""Serialize the `Document` object and insert it in backend."""
...
def load_from_db(self) -> None:
"""Using self.name initialize current document from backend data.
This is responsible for updatinng __dict__ of class with all the fields on doctype."""
...
def db_update(self, *args, **kwargs):
"""Serialize the `Document` object and update existing document in backend."""
...

View file

@ -296,22 +296,25 @@ def make_boilerplate(template, doc, opts=None):
custom_controller = "pass"
if doc.get("is_virtual"):
custom_controller = """
def db_insert(self):
def db_insert(self, *args, **kwargs):
pass
def load_from_db(self):
pass
def db_update(self):
def db_update(self, *args, **kwargs):
pass
def get_list(self, args):
@staticmethod
def get_list(args):
pass
def get_count(self, args):
@staticmethod
def get_count(args):
pass
def get_stats(self, args):
@staticmethod
def get_stats(args):
pass"""
with open(target_file_path, "w") as target: