├── __init__.py ├── requirements.txt ├── CHANGES.txt ├── MANIFEST.in ├── frappeclient ├── __init__.py ├── frappeclient_tests.py └── frappeclient.py ├── .gitignore ├── setup.py ├── LICENSE.txt ├── example.py ├── example2.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2.3.0 2 | ipython 3 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | v0.1, 11th July 2014 -- Initial Release 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include MANIFEST.in 2 | include requirements.txt 3 | recursive-exclude * *.pyc 4 | -------------------------------------------------------------------------------- /frappeclient/__init__.py: -------------------------------------------------------------------------------- 1 | from .frappeclient import FrappeClient 2 | 3 | __all__ = [FrappeClient] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.DS_STORE 3 | *.swp 4 | *.egg-info 5 | *.ropeproject 6 | logply/config.py 7 | /dist 8 | _build/ 9 | MANIFEST 10 | env/ 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | version = '0.1.0dev' 4 | 5 | with open('requirements.txt') as requirements: 6 | install_requires = requirements.read().split() 7 | 8 | setup( 9 | name='frappeclient', 10 | version=version, 11 | author='Rushabh Mehta', 12 | author_email='rushabh@erpnext.com', 13 | packages=[ 14 | 'frappeclient' 15 | ], 16 | install_requires=install_requires, 17 | tests_requires=[ 18 | 'httmock<=1.2.2', 19 | 'nose<=1.3.4' 20 | ], 21 | ) 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Web Notes Technologies Pvt. Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | # Example 2 | # Import job applications from a CSV File (created via Google Forms) to "Job Application" 3 | 4 | import csv 5 | from frappeclient import FrappeClient 6 | 7 | NAME = 2 8 | EMAIL = 3 9 | INTRODUCTION = 4 10 | THOUGHTS_ON_COMPANY = 5 11 | LIKES = 6 12 | LINKS = 7 13 | PHONE = 8 14 | 15 | def sync(): 16 | print("logging in...") 17 | client = FrappeClient("https://xxx.frappecloud.com", "xxx", "xxx") 18 | 19 | with open("jobs.csv", "rU") as jobsfile: 20 | reader = csv.reader(jobsfile, dialect='excel') 21 | for row in reader: 22 | if row[0]=="Timestamp": 23 | continue 24 | 25 | print("finding " + row[EMAIL]) 26 | name = client.get_value("Job Applicant", "name", {"email_id": row[EMAIL]}) 27 | 28 | if name: 29 | doc = client.get_doc("Job Applicant", name["name"]) 30 | else: 31 | doc = {"doctype":"Job Applicant"} 32 | 33 | doc["applicant_name"] = row[NAME] 34 | doc["email_id"] = row[EMAIL] 35 | doc["introduction"] = row[INTRODUCTION] 36 | doc["thoughts_on_company"] = row[THOUGHTS_ON_COMPANY] 37 | doc["likes"] = row[LIKES] 38 | doc["links"] = row[LINKS] 39 | doc["phone_number"] = row[PHONE] 40 | if doc.get("status") != "Rejected": 41 | doc["status"] = "Filled Form" 42 | 43 | if name: 44 | client.update(doc) 45 | print("Updated " + row[EMAIL]) 46 | else: 47 | client.insert(doc) 48 | print("Inserted " + row[EMAIL]) 49 | 50 | if __name__=="__main__": 51 | sync() 52 | -------------------------------------------------------------------------------- /frappeclient/frappeclient_tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest 3 | from frappeclient import FrappeClient 4 | 5 | test_config = dict( 6 | url = 'http://frappe.local:8000', 7 | username = 'Administrator', 8 | password = 'admin' 9 | ) 10 | 11 | TXT = 'test content' 12 | 13 | class TestFrappeClient(unittest.TestCase): 14 | @classmethod 15 | def setUpClass(cls): 16 | cls.conn = FrappeClient(**test_config) 17 | 18 | def test_insert(self): 19 | doc = self.conn.insert(dict(doctype='Note', title='test note 1', content=TXT)) 20 | self.assertEqual(self.conn.get_value('Note', 'content', 21 | dict(title='test note 1'))['content'], TXT) 22 | 23 | self.conn.delete(doctype='Note', name=doc.get('name')) 24 | 25 | def test_list(self): 26 | doc1 = self.conn.insert(dict(doctype='Note', title='apple', content=TXT)) 27 | doc2 = self.conn.insert(dict(doctype='Note', title='banana', content=TXT)) 28 | doc3 = self.conn.insert(dict(doctype='Note', title='carrot', content=TXT)) 29 | 30 | notes = self.conn.get_list('Note', fields=['content', 'name'], filters=dict(title=['like', 'ap%'])) 31 | self.assertEqual(len(notes), 1) 32 | self.assertEqual(notes[0].get('name'), doc1.get('name')) 33 | 34 | self.conn.delete(doctype='Note', name=doc1.get('name')) 35 | self.conn.delete(doctype='Note', name=doc2.get('name')) 36 | self.conn.delete(doctype='Note', name=doc3.get('name')) 37 | 38 | def test_token_auth(self): 39 | self.conn.authenticate('test_key', 'test_secret') 40 | auth_header = self.conn.session.headers.get('Authorization') 41 | self.assertEquals(auth_header, 'Basic dGVzdF9rZXk6dGVzdF9zZWNyZXQ=') 42 | 43 | 44 | if __name__=='__main__': 45 | unittest.main() 46 | -------------------------------------------------------------------------------- /example2.py: -------------------------------------------------------------------------------- 1 | #-*- coding: utf-8 -*- 2 | 3 | from frappeclient import FrappeClient as Client 4 | import os 5 | 6 | files = { 7 | 'CRM': [ 8 | 'Customer', 9 | 'Address', 10 | 'Contact', 11 | 'Customer Group', 12 | 'Territory', 13 | 'Sales Person', 14 | 'Terms and Conditions', 15 | 'Target Detail', 16 | 'Sales Partner', 17 | 'Opportunity', 18 | 'Lead' 19 | ], 20 | 'Manufacturing': [ 21 | 'Production Order', 22 | 'Workstation', 23 | 'Operation', 24 | 'BOM' 25 | ], 26 | 'Selling': [ 27 | 'Quotation', 28 | 'Sales Order', 29 | 'Sales Taxes and Charges', 30 | 'Installation Note', 31 | 'Product Bundle', 32 | ], 33 | 'Stock': [ 34 | 'Purchase Receipt', 35 | 'Bin', 36 | 'Item', 37 | 'Delivery Note', 38 | 'Material Request', 39 | 'Item Variant', 40 | 'Item Attribute Value', 41 | 'Serial No', 42 | 'Warehouse', 43 | 'Stock Entry', 44 | 'Price List', 45 | 'Item Price', 46 | 'Batch', 47 | 'Landed Cost Voucher' 48 | ], 49 | 'Setup': [ 50 | 'UOM', 51 | 'Print Heading', 52 | 'Currency Exchange', 53 | 'Authorization Rule', 54 | 'Authorization Control' 55 | ], 56 | 'Support': [ 57 | 'Issue', 58 | 'Warranty Claim', 59 | 'Maintenance Visit' 60 | ], 61 | 'HR': [ 62 | 'Employee', 63 | 'Job Applicant', 64 | 'Offer Letter', 65 | 'Salary Structure', 66 | 'Leave Application', 67 | 'Expense Claim', 68 | 'Expense Claim Type', 69 | 'Appraisal', 70 | 'Salary Slip', 71 | 'Holiday', 72 | 'Attendance', 73 | 'Leave Type', 74 | 'Job Opening', 75 | 'Designation', 76 | 'Department', 77 | 'Earning Type', 78 | 'Deduction Type', 79 | 'Branch' 80 | ] 81 | } 82 | 83 | def get_path(*args): 84 | path = os.path.abspath(os.path.join( 85 | os.path.dirname(__file__), 'ERPNext', 86 | *args 87 | )) 88 | if not os.path.exists(path): 89 | os.makedirs(path) 90 | 91 | return path 92 | 93 | def download(): 94 | c = Client('http://localhost:8000', 'Administrator', 'admin') 95 | 96 | for k,v in list(files.items()): 97 | for dt in v: 98 | for s, ext, method in (('Schemes', 'pdf', 'get_pdf'), ('Upload Templates', 'csv', 'get_upload_template')): 99 | base_path = get_path(k, s) 100 | with open(os.path.join(base_path, '.'.join([dt, ext])), 'wb') as handler: 101 | fn = getattr(c, method) 102 | if ext == 'pdf': 103 | content = fn('DocType', dt, 'Standard') 104 | else: 105 | try: 106 | content = fn(dt) 107 | except Exception as e: 108 | print('Failed to Download: ' + dt) 109 | continue 110 | 111 | handler.write(content.getvalue()) 112 | print('Downloaded: `{0}` of {1}'.format(ext, dt)) 113 | 114 | if __name__ == '__main__': 115 | download() 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Frappe Client 2 | 3 | Simple Frappe-like Python wrapper for Frappe REST API 4 | 5 | ### Install 6 | 7 | ``` 8 | git clone https://github.com/frappe/frappe-client 9 | pip install -e frappe-client 10 | ``` 11 | 12 | ### API 13 | 14 | FrappeClient has a frappe like API 15 | 16 | #### Login 17 | 18 | Login to the Frappe HTTP Server by creating a new FrappeClient object 19 | 20 | ```py 21 | from frappeclient import FrappeClient 22 | 23 | conn = FrappeClient("example.com") 24 | conn.login("user@example.com", "password") 25 | ``` 26 | 27 | #### Use token based authentication 28 | 29 | ```py 30 | from frappeclient import FrappeClient 31 | 32 | client = FrappeClient("https://example.com") 33 | client.authenticate("my_api_key", "my_api_secret") 34 | ``` 35 | 36 | For demonstration purposes only! Never store any credentials in your source code. Instead, you could set them as environment variables and fetch them with `os.getenv()`. 37 | 38 | #### get_list 39 | 40 | Get a list of documents from the server 41 | 42 | Arguments: 43 | - `doctype` 44 | - `fields`: List of fields to fetch 45 | - `filters`: Dict of filters 46 | - `limit_start`: Start at row ID (default 0) 47 | - `limit_page_length`: Page length 48 | - `order_by`: sort key and order (default is `modified desc`) 49 | 50 | ```py 51 | users = conn.get_list('User', fields = ['name', 'first_name', 'last_name'], , filters = {'user_type':'System User'}) 52 | ``` 53 | 54 | Example of filters: 55 | - `{ "user_type": ("!=", "System User") }` 56 | - `{ "creation": (">", "2020-01-01") }` 57 | - `{ "name": "test@example.com" }` 58 | 59 | #### insert 60 | 61 | Insert a new document to the server 62 | 63 | Arguments: 64 | 65 | - `doc`: Document object 66 | 67 | ```python 68 | doc = conn.insert({ 69 | "doctype": "Customer", 70 | "customer_name": "Example Co", 71 | "customer_type": "Company", 72 | "website": "example.net" 73 | }) 74 | ``` 75 | 76 | #### get_doc 77 | 78 | Fetch a document from the server 79 | 80 | Arguments 81 | - `doctype` 82 | - `name` 83 | 84 | ```py 85 | doc = conn.get_doc('Customer', 'Example Co') 86 | ``` 87 | 88 | #### get_value 89 | 90 | Fetch a single value from the server 91 | 92 | Arguments: 93 | 94 | - `doctype` 95 | - `fieldname` 96 | - `filters` 97 | 98 | ```py 99 | customer_name = conn.get_value("Customer", "name", {"website": "example.net"}) 100 | ``` 101 | 102 | #### update 103 | 104 | Update a document (if permitted) 105 | 106 | Arguments: 107 | - `doc`: JSON document object 108 | 109 | ```py 110 | doc = conn.get_doc('Customer', 'Example Co') 111 | doc['phone'] = '000000000' 112 | conn.update(doc) 113 | ``` 114 | 115 | #### delete 116 | 117 | Delete a document (if permitted) 118 | 119 | Arguments: 120 | - `doctype` 121 | - `name` 122 | 123 | ```py 124 | conn.delete('Customer', 'Example Co') 125 | ``` 126 | 127 | ### Example 128 | 129 | ```python 130 | from frappeclient import FrappeClient 131 | 132 | conn = FrappeClient("example.com", "user@example.com", "password") 133 | new_notes = [ 134 | {"doctype": "Note", "title": "Sing", "public": True}, 135 | {"doctype": "Note", "title": "a", "public": True}, 136 | {"doctype": "Note", "title": "Song", "public": True}, 137 | {"doctype": "Note", "title": "of", "public": True}, 138 | {"doctype": "Note", "title": "sixpence", "public": True} 139 | ] 140 | 141 | for note in new_notes: 142 | print(conn.insert(note)) 143 | 144 | # get note starting with s 145 | notes = conn.get_list('Note', 146 | filters={'title': ('like', 's')}, 147 | fields=["title", "public"] 148 | ) 149 | ``` 150 | 151 | ### Example 152 | 153 | See example.py for more info 154 | 155 | ### License 156 | 157 | MIT 158 | -------------------------------------------------------------------------------- /frappeclient/frappeclient.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import sys ### FAD Prepare to exit program 4 | from base64 import b64encode ### FAD to make token work 5 | 6 | #from urllib.parse import quote ### Commented by FAD for compatibility on python3 7 | from six.moves.urllib.parse import quote ### FAD for compatibility on python3 8 | from six.moves import urllib ### FAD for compatibility on python3 9 | 10 | try: 11 | from StringIO import StringIO 12 | except: 13 | from io import StringIO 14 | 15 | try: 16 | unicode 17 | except NameError: 18 | unicode = str 19 | 20 | 21 | class AuthError(Exception): 22 | pass 23 | 24 | 25 | class FrappeException(Exception): 26 | pass 27 | 28 | 29 | class NotUploadableException(FrappeException): 30 | def __init__(self, doctype): 31 | self.message = "The doctype `{1}` is not uploadable, so you can't download the template".format(doctype) 32 | 33 | 34 | class FrappeClient(object): 35 | def __init__(self, url=None, username=None, password=None, api_key=None, api_secret=None, verify=True): 36 | self.headers = dict(Accept='application/json') 37 | self.session = requests.Session() 38 | self.can_download = [] 39 | self.verify = verify 40 | self.url = url 41 | 42 | if username and password: 43 | self.login(username, password) 44 | 45 | if api_key and api_secret: 46 | self.authenticate(api_key, api_secret) 47 | 48 | def __enter__(self): 49 | return self 50 | 51 | def __exit__(self, *args, **kwargs): 52 | self.logout() 53 | 54 | def login(self, username, password): 55 | r = self.session.post(self.url, data={ 56 | 'cmd': 'login', 57 | 'usr': username, 58 | 'pwd': password 59 | }, verify=self.verify, headers=self.headers) 60 | 61 | if r.json().get('message') == "Logged In": 62 | self.can_download = [] 63 | return r.json() 64 | else: 65 | raise AuthError 66 | 67 | def authenticate(self, api_key, api_secret, test_success=False): 68 | token = b64encode('{}:{}'.format(api_key, api_secret).encode()).decode() ### FAD for working token 69 | auth_header = {'Authorization': 'Basic {}'.format(token)} 70 | self.session.headers.update(auth_header) 71 | 72 | #if test_success: ### FAD for working token 73 | # If the authorization isn't successful AuthError is raised in post process 74 | # self.get_api("frappe.auth.get_logged_user") ### FAD for working token 75 | 76 | def logout(self): 77 | self.session.get(self.url, params={ 78 | 'cmd': 'logout', 79 | }) 80 | 81 | def get_list(self, doctype, fields='"*"', filters=None, limit_start=0, limit_page_length=0, order_by=None): 82 | '''Returns list of records of a particular type''' 83 | if not isinstance(fields, unicode): 84 | fields = json.dumps(fields) 85 | params = { 86 | "fields": fields, 87 | } 88 | if filters: 89 | params["filters"] = json.dumps(filters) 90 | if limit_page_length: 91 | params["limit_start"] = limit_start 92 | params["limit_page_length"] = limit_page_length 93 | if order_by: 94 | params['order_by'] = order_by 95 | 96 | res = self.session.get(self.url + "/api/resource/" + doctype, params=params, 97 | verify=self.verify, headers=self.headers) 98 | return self.post_process(res) 99 | 100 | def insert(self, doc): 101 | '''Insert a document to the remote server 102 | 103 | :param doc: A dict or Document object to be inserted remotely''' 104 | res = self.session.post(self.url + "/api/resource/" + quote(doc.get("doctype")), 105 | data={"data":json.dumps(doc)}) 106 | return self.post_process(res) 107 | 108 | def insert_many(self, docs): 109 | '''Insert multiple documents to the remote server 110 | 111 | :param docs: List of dict or Document objects to be inserted in one request''' 112 | return self.post_request({ 113 | "cmd": "frappe.client.insert_many", 114 | "docs": frappe.as_json(docs) 115 | }) 116 | 117 | def update(self, doc): 118 | '''Update a remote document 119 | 120 | :param doc: dict or Document object to be updated remotely. `name` is mandatory for this''' 121 | url = self.url + "/api/resource/" + quote(doc.get("doctype")) + "/" + quote(doc.get("name")) 122 | res = self.session.put(url, data={"data":json.dumps(doc)}) 123 | return self.post_process(res) 124 | 125 | def bulk_update(self, docs): 126 | '''Bulk update documents remotely 127 | 128 | :param docs: List of dict or Document objects to be updated remotely (by `name`)''' 129 | return self.post_request({ 130 | 'cmd': 'frappe.client.bulk_update', 131 | 'docs': json.dumps(docs) 132 | }) 133 | 134 | def delete(self, doctype, name): 135 | '''Delete remote document by name 136 | 137 | :param doctype: `doctype` to be deleted 138 | :param name: `name` of document to be deleted''' 139 | return self.post_request({ 140 | 'cmd': 'frappe.client.delete', 141 | 'doctype': doctype, 142 | 'name': name 143 | }) 144 | 145 | def submit(self, doclist): 146 | '''Submit remote document 147 | 148 | :param doc: dict or Document object to be submitted remotely''' 149 | return self.post_request({ 150 | 'cmd': 'frappe.client.submit', 151 | 'doclist': json.dumps(doclist) 152 | }) 153 | 154 | def get_value(self, doctype, fieldname=None, filters=None): 155 | return self.get_request({ 156 | 'cmd': 'frappe.client.get_value', 157 | 'doctype': doctype, 158 | 'fieldname': fieldname or 'name', 159 | 'filters': json.dumps(filters) 160 | }) 161 | 162 | def set_value(self, doctype, docname, fieldname, value): 163 | return self.post_request({ 164 | 'cmd': 'frappe.client.set_value', 165 | 'doctype': doctype, 166 | 'name': docname, 167 | 'fieldname': fieldname, 168 | 'value': value 169 | }) 170 | 171 | def cancel(self, doctype, name): 172 | return self.post_request({ 173 | 'cmd': 'frappe.client.cancel', 174 | 'doctype': doctype, 175 | 'name': name 176 | }) 177 | 178 | def get_doc(self, doctype, name="", filters=None, fields=None): 179 | '''Returns a single remote document 180 | 181 | :param doctype: DocType of the document to be returned 182 | :param name: (optional) `name` of the document to be returned 183 | :param filters: (optional) Filter by this dict if name is not set 184 | :param fields: (optional) Fields to be returned, will return everythign if not set''' 185 | params = {} 186 | if filters: 187 | params["filters"] = json.dumps(filters) 188 | if fields: 189 | params["fields"] = json.dumps(fields) 190 | 191 | res = self.session.get(self.url + '/api/resource/' + doctype + '/' + name, 192 | params=params) 193 | 194 | return self.post_process(res) 195 | 196 | def rename_doc(self, doctype, old_name, new_name): 197 | '''Rename remote document 198 | 199 | :param doctype: DocType of the document to be renamed 200 | :param old_name: Current `name` of the document to be renamed 201 | :param new_name: New `name` to be set''' 202 | params = { 203 | 'cmd': 'frappe.client.rename_doc', 204 | 'doctype': doctype, 205 | 'old_name': old_name, 206 | 'new_name': new_name 207 | } 208 | return self.post_request(params) 209 | 210 | def get_pdf(self, doctype, name, print_format='Standard', letterhead=True): 211 | params = { 212 | 'doctype': doctype, 213 | 'name': name, 214 | 'format': print_format, 215 | 'no_letterhead': int(not bool(letterhead)) 216 | } 217 | response = self.session.get( 218 | self.url + '/api/method/frappe.templates.pages.print.download_pdf', 219 | params=params, stream=True) 220 | 221 | return self.post_process_file_stream(response) 222 | 223 | def get_html(self, doctype, name, print_format='Standard', letterhead=True): 224 | params = { 225 | 'doctype': doctype, 226 | 'name': name, 227 | 'format': print_format, 228 | 'no_letterhead': int(not bool(letterhead)) 229 | } 230 | response = self.session.get( 231 | self.url + '/print', params=params, stream=True 232 | ) 233 | return self.post_process_file_stream(response) 234 | 235 | def __load_downloadable_templates(self): 236 | self.can_download = self.get_api('frappe.core.page.data_import_tool.data_import_tool.get_doctypes') 237 | 238 | def get_upload_template(self, doctype, with_data=False): 239 | if not self.can_download: 240 | self.__load_downloadable_templates() 241 | 242 | if doctype not in self.can_download: 243 | raise NotUploadableException(doctype) 244 | 245 | params = { 246 | 'doctype': doctype, 247 | 'parent_doctype': doctype, 248 | 'with_data': 'Yes' if with_data else 'No', 249 | 'all_doctypes': 'Yes' 250 | } 251 | 252 | request = self.session.get( 253 | self.url + '/api/method/frappe.core.page.data_import_tool.exporter.get_template', 254 | params=params 255 | ) 256 | return self.post_process_file_stream(request) 257 | 258 | def get_api(self, method, params={}): 259 | res = self.session.get(self.url + '/api/method/' + method + '/', params=params) 260 | return self.post_process(res) 261 | 262 | def post_api(self, method, params={}): 263 | res = self.session.post(self.url + '/api/method/' + method + '/', params=params) 264 | return self.post_process(res) 265 | 266 | def get_request(self, params): 267 | res = self.session.get(self.url, params=self.preprocess(params)) 268 | res = self.post_process(res) 269 | return res 270 | 271 | def post_request(self, data): 272 | res = self.session.post(self.url, data=self.preprocess(data)) 273 | res = self.post_process(res) 274 | return res 275 | 276 | def preprocess(self, params): 277 | '''convert dicts, lists to json''' 278 | for key, value in params.items(): ##Substituted iteritems for items in python3 279 | if isinstance(value, (dict, list)): 280 | params[key] = json.dumps(value) 281 | 282 | return params 283 | 284 | def post_process(self, response): 285 | try: 286 | rjson = response.json() 287 | except ValueError: 288 | print(response.text) 289 | sys.exit() ### FAD Exit program in case of exception such as updating document when changed after downloaded 290 | ###raise ### FAD 291 | 292 | if rjson and ('exc' in rjson) and rjson['exc']: 293 | raise FrappeException(rjson['exc']) 294 | if 'message' in rjson: 295 | return rjson['message'] 296 | elif 'data' in rjson: 297 | return rjson['data'] 298 | else: 299 | return None 300 | 301 | def post_process_file_stream(self, response): 302 | if response.ok: 303 | output = StringIO() 304 | for block in response.iter_content(1024): 305 | output.write(block) 306 | return output 307 | 308 | else: 309 | try: 310 | rjson = response.json() 311 | except ValueError: 312 | print(response.text) 313 | raise 314 | 315 | if rjson and ('exc' in rjson) and rjson['exc']: 316 | raise FrappeException(rjson['exc']) 317 | if 'message' in rjson: 318 | return rjson['message'] 319 | elif 'data' in rjson: 320 | return rjson['data'] 321 | else: 322 | return None 323 | --------------------------------------------------------------------------------