diff --git a/frappe/client.py b/frappe/client.py index 81bec3787c..d9e5a0a806 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -8,14 +8,33 @@ import frappe.model import frappe.utils import json, os +''' +Handle RESTful requests that are mapped to the `/api/resource` route. + +Requests via FrappeClient are also handled here. +''' + @frappe.whitelist() def get_list(doctype, fields=None, filters=None, order_by=None, limit_start=None, limit_page_length=20): + '''Returns a list of records by filters, fields, ordering and limit + + :param doctype: DocType of the data to be queried + :param fields: fields to be returned. Default is `name` + :param filters: filter list by this dict + :param order_by: Order by this fieldname + :param limit_start: Start at this index + :param limit_page_length: Number of records to be returned (default 20)''' return frappe.get_list(doctype, fields=fields, filters=filters, order_by=order_by, limit_start=limit_start, limit_page_length=limit_page_length, ignore_permissions=False) @frappe.whitelist() def get(doctype, name=None, filters=None): + '''Returns a document by name or filters + + :param doctype: DocType of the document to be returned + :param name: return document of this `name` + :param filters: If name is not set, filter by these values and return the first match''' if filters and not name: name = frappe.db.get_value(doctype, json.loads(filters)) if not name: @@ -29,6 +48,12 @@ def get(doctype, name=None, filters=None): @frappe.whitelist() def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False): + '''Returns a value form a document + + :param doctype: DocType to be queried + :param fieldname: Field to be returned (default `name`) + :param filters: dict or string for identifying the record''' + if not frappe.has_permission(doctype): frappe.throw(_("Not permitted"), frappe.PermissionError) @@ -80,6 +105,9 @@ def set_value(doctype, name, fieldname, value=None): @frappe.whitelist() def insert(doc=None): + '''Insert a document + + :param doc: JSON or dict object to be inserted''' if isinstance(doc, basestring): doc = json.loads(doc) @@ -95,6 +123,9 @@ def insert(doc=None): @frappe.whitelist() def insert_many(docs=None): + '''Insert multiple documents + + :param docs: JSON or list of dict objects to be inserted in one request''' if isinstance(docs, basestring): docs = json.loads(docs) @@ -118,6 +149,9 @@ def insert_many(docs=None): @frappe.whitelist() def save(doc): + '''Update (save) an existing document + + :param doc: JSON or dict object with the properties of the document to be updated''' if isinstance(doc, basestring): doc = json.loads(doc) @@ -126,11 +160,19 @@ def save(doc): @frappe.whitelist() def rename_doc(doctype, old_name, new_name, merge=False): + '''Rename document + + :param doctype: DocType of the document to be renamed + :param old_name: Current `name` of the document to be renamed + :param new_name: New `name` to be set''' new_name = frappe.rename_doc(doctype, old_name, new_name, merge=merge) return new_name @frappe.whitelist() def submit(doc): + '''Submit a document + + :param doc: JSON or dict object to be submitted remotely''' if isinstance(doc, basestring): doc = json.loads(doc) @@ -141,6 +183,10 @@ def submit(doc): @frappe.whitelist() def cancel(doctype, name): + '''Cancel a document + + :param doctype: DocType of the document to be cancelled + :param name: name of the document to be cancelled''' wrapper = frappe.get_doc(doctype, name) wrapper.cancel() @@ -148,6 +194,10 @@ def cancel(doctype, name): @frappe.whitelist() def delete(doctype, name): + '''Delete a remote document + + :param doctype: DocType of the document to be deleted + :param name: name of the document to be deleted''' frappe.delete_doc(doctype, name) @frappe.whitelist() @@ -158,6 +208,9 @@ def set_default(key, value, parent=None): @frappe.whitelist() def make_width_property_setter(doc): + '''Set width Property Setter + + :param doc: Property Setter document with `width` property''' if isinstance(doc, basestring): doc = json.loads(doc) if doc["doctype"]=="Property Setter" and doc["property"]=="width": @@ -165,6 +218,9 @@ def make_width_property_setter(doc): @frappe.whitelist() def bulk_update(docs): + '''Bulk update documents + + :param docs: JSON list of documents to be updated remotely. Each document must have `docname` property''' docs = json.loads(docs) failed_docs = [] for doc in docs: @@ -184,17 +240,32 @@ def bulk_update(docs): @frappe.whitelist() def has_permission(doctype, docname, perm_type="read"): + '''Returns a JSON with data whether the document has the requested permission + + :param doctype: DocType of the document to be checked + :param docname: `name` of the document to be checked + :param perm_type: one of `read`, `write`, `create`, `submit`, `cancel`, `report`. Default is `read`''' # perm_type can be one of read, write, create, submit, cancel, report return {"has_permission": frappe.has_permission(doctype, perm_type.lower(), docname)} @frappe.whitelist() def get_password(doctype, name, fieldname): + '''Return a password type property. Only applicable for System Managers + + :param doctype: DocType of the document that holds the password + :param name: `name` of the document that holds the password + :param fieldname: `fieldname` of the password property + ''' frappe.only_for("System Manager") return frappe.get_doc(doctype, name).get_password(fieldname) @frappe.whitelist() def get_js(items): + '''Load JS code files. Will also append translations + and extend `frappe._messages` + + :param items: JSON list of paths of the js files to be loaded.''' items = json.loads(items) out = [] for src in items: diff --git a/frappe/frappeclient.py b/frappe/frappeclient.py index 855564e8fc..10c5f505ba 100644 --- a/frappe/frappeclient.py +++ b/frappe/frappeclient.py @@ -2,6 +2,13 @@ import requests import json import frappe +''' +FrappeClient is a library that helps you connect with other frappe systems + + + +''' + class AuthError(Exception): pass @@ -13,7 +20,7 @@ class FrappeClient(object): self.verify = verify self.session = requests.session() self.url = url - self.login(username, password) + self._login(username, password) def __enter__(self): return self @@ -21,7 +28,8 @@ class FrappeClient(object): def __exit__(self, *args, **kwargs): self.logout() - def login(self, username, password): + def _login(self, username, password): + '''Login/start a sesion. Called internally on init''' r = self.session.post(self.url, data={ 'cmd': 'login', 'usr': username, @@ -34,6 +42,7 @@ class FrappeClient(object): raise AuthError def logout(self): + '''Logout session''' self.session.get(self.url, params={ 'cmd': 'logout', }, verify=self.verify) @@ -54,41 +63,65 @@ class FrappeClient(object): return self.post_process(res) def insert(self, doc): + '''Insert a document to the remote server + + :param doc: A dict or Document object to be inserted remotely''' res = self.session.post(self.url + "/api/resource/" + doc.get("doctype"), data={"data":frappe.as_json(doc)}, verify=self.verify) return self.post_process(res) def insert_many(self, docs): + '''Insert multiple documents to the remote server + + :param docs: List of dict or Document objects to be inserted in one request''' return self.post_request({ "cmd": "frappe.client.insert_many", "docs": frappe.as_json(docs) }) def update(self, doc): + '''Update a remote document + + :param doc: dict or Document object to be updated remotely. `name` is mandatory for this''' url = self.url + "/api/resource/" + doc.get("doctype") + "/" + doc.get("name") res = self.session.put(url, data={"data":frappe.as_json(doc)}, verify=self.verify) return self.post_process(res) def bulk_update(self, docs): + '''Bulk update documents remotely + + :param docs: List of dict or Document objects to be updated remotely (by `name`)''' return self.post_request({ "cmd": "frappe.client.bulk_update", "docs": frappe.as_json(docs) }) def delete(self, doctype, name): + '''Delete remote document by name + + :param doctype: `doctype` to be deleted + :param name: `name` of document to be deleted''' return self.post_request({ "cmd": "frappe.model.delete_doc", "doctype": doctype, "name": name }) - def submit(self, doclist): + def submit(self, doc): + '''Submit remote document + + :param doc: dict or Document object to be submitted remotely''' return self.post_request({ "cmd": "frappe.client.submit", - "doclist": frappe.as_json(doclist) + "doc": frappe.as_json(doc) }) def get_value(self, doctype, fieldname=None, filters=None): + '''Returns a value form a document + + :param doctype: DocType to be queried + :param fieldname: Field to be returned (default `name`) + :param filters: dict or string for identifying the record''' return self.get_request({ "cmd": "frappe.client.get_value", "doctype": doctype, @@ -97,6 +130,12 @@ class FrappeClient(object): }) def set_value(self, doctype, docname, fieldname, value): + '''Set a value in a remote document + + :param doctype: DocType of the document to be updated + :param docname: name of the document to be updated + :param fieldname: fieldname of the document to be updated + :param value: value to be updated''' return self.post_request({ "cmd": "frappe.client.set_value", "doctype": doctype, @@ -106,6 +145,10 @@ class FrappeClient(object): }) def cancel(self, doctype, name): + '''Cancel a remote document + + :param doctype: DocType of the document to be cancelled + :param name: name of the document to be cancelled''' return self.post_request({ "cmd": "frappe.client.cancel", "doctype": doctype, @@ -113,6 +156,12 @@ class FrappeClient(object): }) def get_doc(self, doctype, name="", filters=None, fields=None): + '''Returns a single remote document + + :param doctype: DocType of the document to be returned + :param name: (optional) `name` of the document to be returned + :param filters: (optional) Filter by this dict if name is not set + :param fields: (optional) Fields to be returned, will return everythign if not set''' params = {} if filters: params["filters"] = json.dumps(filters) @@ -125,6 +174,11 @@ class FrappeClient(object): return self.post_process(res) def rename_doc(self, doctype, old_name, new_name): + '''Rename remote document + + :param doctype: DocType of the document to be renamed + :param old_name: Current `name` of the document to be renamed + :param new_name: New `name` to be set''' params = { "cmd": "frappe.client.rename_doc", "doctype": doctype, diff --git a/frappe/utils/autodoc.py b/frappe/utils/autodoc.py index 4a1412598e..0452c9a1b1 100644 --- a/frappe/utils/autodoc.py +++ b/frappe/utils/autodoc.py @@ -137,10 +137,21 @@ def parse(docs): def strip_leading_tabs(docs): """Strip leading tabs from __doc__ text.""" lines = docs.splitlines() + + # remove empty lines in the front + start = 0 + for line in lines: + if line != '': break + start += 1 + if start: + lines = lines[start:] + + # remove default indentation if len(lines) > 1: start_line = 1 ref_line = lines[start_line] while not ref_line: + # find reference line for indentations (the first line that is nonempty (false)) start_line += 1 if start_line > len(lines): break ref_line = lines[start_line]