Frappe.call allowed args

What datatypes can I pass from client to server?
This says:

args: associative array, arguments that will pass with request.

Do I have the right documentation? On the server side, should I set up the method with a parameter to accept this associative array/ dictionary? Like this:

def im_a_python_method(self, im_a_js_dict):
    # execute sweet code
    return awesomeness
1 Like

Whatever you pass in args in frappe.call will be mapped to the named parameters on the corresponding python function. Data types will be literal, so string, float, integer.

Objects will automatically be converted to JSON

1 Like

So the call would look like:

frappe.call({
			method: "im_a_python_method",
			args: im_a_js_dict,
			callback: function (r) ...

And the server side would look like:

def im_a_python_method(self, im_a_js_dict):
    frappe.msgprint("testing method", im_a_js_dict)

Iā€™m getting a 404 and I donā€™t know why.

Found it:

frappe.call({
			method: "im_a_python_method",
            doc: cur_frm.doc, // this line is required
			args: im_a_js_dict,
			callback: function (r) ...

That was a pain.

I am finding that this is not what is happening (I think). The data structure once in python seems inside out: [{key,value},{key:value},{key:value}]. This isnā€™t pure JSON (because itā€™s a list and not a string?). Iā€™m wondering if thereā€™s a frappe function that unpacks this into a dictionary correctly. The JSON library doesnā€™t seem to have a decoder that allows this format. I tried searching the documentation but have come up dry. is there a ā€œas_dictā€ option for frappe.call?

If you are passing a dict to a parameter, it will be converted to JSON.

On the server you can load it with json.loads

Alternatively you can pass the keys of your dict as parameters on the function

1 Like

Thanks @rmehta. I am able to call str() on the argument:

[{u'Americauna': u'5'}, {u'Barred Rocks': u'6'}, {u'Rhode Island Reds': u'7'}]

I am passing an associative array via function argument, as prototyped above. As you can see from the output I have two barriers keeping me from a python dictionary: the unicode thing and the inside-out dictionary thing (itā€™s literally an array of single-element-dictionaries, which is the kind problem you create when you translate from one programming language to another.)
Edit: I was constructing the array incorrectly that led to the weird structure

Iā€™m wondering if there isnā€™t a clever way to do this regular expressionsā€¦
Edit: This is a terrible idea.

Notably, json.load or json.JSONDecode both fail - saying that they cannot load/decode a list, when the documentation explicitly says that it can. I think Iā€™d prefer these methods to rolling my own because the built-in integer and float parsing seems really useful.
Edit: Because my data construction was wrong, I was misdiagnosing my error. I have a new problem in the same vein

Now that I am actually sending JSON, frappe wants the JSON to be keyword arguments, which is not what Iā€™m after. I just want the whole object. I am getting this error:
TypeError: im_a_python_method() got an unexpected keyword argument ā€˜First Key Valueā€™

try this:

Python

import frappe, json

@frappe.whitelist(allow_guest=True)
def data_types(*args, **kwargs):
	for i in kwargs.iteritems():
		if i[0] not in ['cmd']: # cmd, data, method
			try:
				print("key = {0}, value = {1}, type_of_value = {2}".format(i[0], 
																	json.loads(i[1]),
																	 type(json.loads(i[1]))))
			except Exception as e:
				print ("ERROR", i[1]) # This will print what didn't decode as expected

	return kwargs

JS

frappe.call({
    method:"path.to.api.data_types",
    args: {
        list: ["a","b","c"],
        dict: {"a":"A","b":"B"},
        list_of_dict: [{"a":"A","b":"B"},{"c":"C","d":"D"}]
    }
})
.fail(fail => console.log("failure", fail))
.done(success => console.log("success", success.message));
3 Likes

Thanks @revant_one. Iā€™m getting a new error I donā€™t understand. If you prefer we can take this one offline.

desk.min.js:887 Uncaught TypeError: data.message.search is not a function
    at Object.frappe.msgprint (desk.min.js:887)
    at desk.min.js:850
    at Array.forEach (<anonymous>)
    at Object.frappe.msgprint (desk.min.js:849)
    at Object.frappe.request.cleanup (desk.min.js:1384)
    at Object.<anonymous> (desk.min.js:1325)
    at i (jquery.min.js:2)
    at Object.fireWith [as resolveWith] (jquery.min.js:2)
    at z (jquery.min.js:4)
    at XMLHttpRequest.<anonymous> (jquery.min.js:4)

Share the code you tried?

What are you using to print output? console.log or frappe.msgprint? Try with console.log first.

JavaScript:

frappe.provide("erpnext.utils");

laying_flocks = {};
var wrapper = $(cur_frm.fields_dict['htmlpopulate'].wrapper);
cur_frm.set_value('batched_egg_yield', '');
cur_frm.set_value('collection_date', moment());
// ! also set today date  as default

// Render template of flocks with text input fields to tally the eggs collected
frappe.ui.form.on('Egg Collection', {
	onload: function (frm) {
		frappe.call({
			method: "get_laying_flocks",
			doc: cur_frm.doc,
			callback: function (r) {
				//console.log(r.message);
				laying_flocks = r.message;
				wrapper.html(frappe.render_template("eggcollection", {"laying flocks" : laying_flocks}))
				}
		});
		// fetch and populate egg defaults
		frappe.call({
			method: "egg_defaults",
			doc: cur_frm.doc,
			callback: function (r) {
				cur_frm.set_value('at_enter_as_item', r.message[0].egg_stock_item);
				cur_frm.set_value('at_enter_as_uom', r.message[0].egg_default_uom);
				cur_frm.set_value('destination_warehouse',r.message[0].egg_default_storage_location);
			}
		})
	}
});

// pass to server on validate
frappe.ui.form.on('Egg Collection', {
	validate: function (frm) {
		egg_yields = (wrapper.find(".egg_yield"));
		collected_eggs = [];
		batched_egg_yield = 0;
		// assign to global egg yield dict
		for (i in laying_flocks) {
			egg_count = parseInt(egg_yields[i].value)
			collected_eggs.push(laying_flocks[i].name, egg_count)
			batched_egg_yield += egg_count || 0;
		};

		var egg_dict = {};
		collected_eggs.forEach(function(val, i) {
    	if (i % 2 === 1) return; // Skip all even elements (= odd indexes)
    	egg_dict[val] = collected_eggs[i + 1];   // Assign the next element as a value of the object,using the current value as key
		});

		cur_frm.set_value('batched_egg_yield', batched_egg_yield);

		console.log(egg_dict)
		frappe.call({
			method: "parse_data_types",
			doc: cur_frm.doc,
			args: JSON.stringify(egg_dict), // send egg yield
    })
		.fail(fail => console.log("failure", fail))
		.done(success => console.log("success", success.message));
  }
})

Python:

from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
import json


class EggCollection(Document):
    def get_laying_flocks(self):
        flock_dict = frappe.get_all("Poultry Flock",
            fields=["name"],
            filters={"flock_purpose": "Laying"},
            order_by='name')
        return flock_dict

    def egg_defaults(self):
        return frappe.db.get_values_from_single(["egg_default_uom","egg_default_storage_location","egg_stock_item"], None, "Livestock Settings", as_dict=True)

    def collect_egg_yield(self, collected_eggs):
        print collected_eggs
        # frappe.msgprint(str(collected_eggs), "collected_eggs")
        # collected_eggs = json.loads(collected_eggs, parse_float=True, parse_int=True)
        # frappe.msgprint(collected_eggs)
        #for key, value in collected_eggs:
        #    yield make_egg_detail(self, collected_eggs)
        # frappe.msgprint(str(collected_eggs),"Egg Collection Records Saved")
        #pass_to_stock(self)

    def make_egg_detail(self, collected_eggs):
        make_egg_detail = frappe.new_doc('Egg Collection Detail')
        make_egg_detail.poultry_flock = collected_eggs[key]
        make_egg_detail.egg_yield = collected_eggs[value]
        make_egg_detail.date_collected = self.collection_date
        make_egg_detail.save()
        # enter the rest of of the detail here

    def pass_to_stock(self):
        # this appears to be working except for the conversion table
        pass_to_stock=frappe.new_doc('Stock Entry')
        pass_to_stock.purpose = "Material Receipt"
        pass_to_stock.append("items", {
            "item_code": self.at_enter_as_item,
            "qty": self.batched_egg_yield,
            "uom": "Nos",
            "stock_uom": self.at_enter_as_uom,
            "conversion_factor": "1",
            "transfer_qty": self.batched_egg_yield
        })
        pass_to_stock.company = frappe.db.get_value("Global Defaults", None, "default_company")
        pass_to_stock.to_warehouse = "Finished Goods - " + str((frappe.db.get_value("Company", pass_to_stock.company, "abbr")))
        pass_to_stock.date = self.collection_date
        # pass_to_stock.time = self.time_collected
        pass_to_stock.save()
        frappe.msgprint("Material Receipt Saved")

    def parse_data_types(*args,**kwargs):
        frappe.msgprint(kwargs,"parse data types")
        for i in kwargs.iteritems():
            if i[0] not in ['cmd']: # cmd, data, method
                try:
                    print("key = {0}, value = {1}, type_of_value = {2}".format(i[0], json.loads(i[1]), type(json.loads(i[1]))))
                except Exception as e:
                    print ("ERROR", i[1]) # This will print what didn't decode as expected
        # print kwargs
        # frappe.msgprint(str(kwargs),"parse data types")
        return kwargs # self.collect_egg_yield(kwargs)

I was fighting with ā€œargsā€ again today and I overlooked something:

So if you pass it "args": {"docname": cur_frm.doc.linked_doc}
And then have:
def i_am_a_python_method(doc):
on the other side, it will give you the takes exactly 1 argument (0 given) error, because it is trying to map to a variable named ā€œdocnameā€ and all it can find is one named ā€œdocā€. Make them match. I wasted way too much time on this, thinking that it should just rename it on the fly. That was bad programming on my part.
Once you get the hang of it, itā€™s really convenient.

3 Likes

My arguments returns empty dict although it has an arguments. Why does it happening? Kindly help

Hey @ibalajib, please expand your problem statement. This specific issue describes inbound arguments, not the return value. Sounds like you should start a new topic (with more details) and link to it here.