Can someone help me understand why the following reduced example isn’t behaving as I expect it to?
I have two interconnected data tables (table_0 and table_1) and two simple Server Modules that do some very basic DB manipulation. (Let’s assume this setup makes sense in the bigger context of the app I’m building.)
Server Module 1:
@anvil.server.callable
def test():
entry = app_tables.table_0.add_row(name=str(random.randint(1000,10000)))
print('--- test 1 ---')
print(entry['t1'])
print()
anvil.server.call('create_row', entry)
print('--- test 2 ---')
print(entry['t1'])
print()
Server Module 2:
@anvil.server.callable
def create_row(entry):
print('--- create_row 1 ---')
print(entry['t1'])
print()
row = entry['t1']
if row is None:
row = app_tables.table_1.add_row(t0=entry)
entry['t1'] = row
print('--- create_row 2 ---')
print(entry['t1'])
print()
When the test() function is invoked (by a button click), this is what’s being printed:
--- test 1 ---
None
--- create_row 1 ---
None
--- create_row 2 ---
<LiveObject: anvil.tables.Row>
--- test 2 ---
None
All expected except the last one: Why does the entry in the test() scope not know about the new reference in the t1 column? Shouldn’t both entry variables even simply point to the same Row object? (Row objects are portable according to the docs and get_id() indeed always returns the same id.)
What am I missing?
Clone link:
https://anvil.works/build#clone:BMYJJEMPS5RQ4FDY=A6X3YHTHJEPIYRQ75GLNSZSR
1 Like
You use anvil.server.call()
when you want to call a server function from the client. Anvil will take care of serializing the arguments, send them to the server, deserialize them and create new objects on the server, run the function in the server with the new objects just created, return some values (serialize-send-deserialize) to the client’s interpreter. The objects on the client and on the server are obviously different. They exist in different interpreters in different machines.
If you call a function defined in a server module from another server module using the same anvil.server.call()
, you end up with the serialize-send-deserialize thingy and the objects on the two functions are independent, they don’t know each other.
You should simply import server module 2 in server module 1 and call the function. Let me know if this is not clear enough and I will go to my pc, clone your app and show you the code (I’m on my cell now).
2 Likes
Thank you, Stefano. Being new to Anvil and web app development in general, this makes things a lot clearer for me. I think I know how to do this instead in my app, and a takeaway for me would also be to avoid anvil.server.call()
unless I’m calling a server function from the client (or maybe if necessary from my local code via Uplink, which is what I was actually doing in this example), also for performance reasons.
One part I’m still struggling with a bit: I understand that the entry
objects are independent after the serialize-send-deserialize process, but somehow they still both point to the same row in my Data Table, because the following works (as I can verify manually in the IDE):
@anvil.server.callable
def test():
entry = app_tables.table_0.add_row(name=str(random.randint(1000,10000)))
anvil.server.call('add_data', entry)
@anvil.server.callable
def add_data(entry):
entry['data'] = 555
Under the hood, is this done via the ID I can also get with entry.get_id()
, which is of ocurse preserved in the serialize-send-deserialize process? Can I then use this ID to “pass” DB rows via Uplink?
1 Like
Yes
Yes, that’s part of the magic Anvil does for you.
Your app’s code runs on several interpreters: the server’s real python, the browser’s python-ish Skulpt, plus as many uplinks as you like, all running on different machines or in different python versions.
The Anvil libraries take care of passing objects from interpreter to interpreter, passing exceptions (info about errors, which line they happened on, etc.), database transactions, etc.
If your code is between uplink and server module, you could have the module 2 returning the entry
object to the module 1, so after the call you have a new object that knows about the latest changes:
entry = anvil.server.call('create_row', entry)
1 Like
Great, thank you, this is really helpful.
Is there some difference in how anvil.server.call
works depending on whether it’s called from client vs. server (or Uplink)? Because related to my initial question, I did the following experiment:
Server Module:
@anvil.server.callable
def test():
entry = anvil.server.call('generate_entry')
anvil.server.call('add_data', entry)
print(entry['data'])
@anvil.server.callable
def generate_entry():
return app_tables.table_0.add_row(name=str(random.randint(1000,10000)))
@anvil.server.callable
def add_data(entry):
entry['data'] = 555
Client:
def button_1_click(self, **event_args):
entry = anvil.server.call('generate_entry')
server.call('add_data', entry)
print(entry['data'])
def button_2_click(self, **event_args):
anvil.server.call('test')
When I click button 1, I get 555
, when I click button 2 I get None
. I would expect it to be None
in both cases as per the (de-)serialization process you explained above?
1 Like
I don’t know the details.
Here we are dealing with the magic of anvil.server.call()
and the magic of row objects, and I don’t know the details of what affects what.
Row objects try to minimize the number of round trips, the number of interactions with the database and the amount of data they store and transfer between server and client. These things are in conflict: you could get lots of data in one db interaction and in one round trip, but you may end up transferring hundreds of megabytes when you need just a few bytes. So Anvil tries its best guessing what you need.
In this case it could find the 555
because the data
column is of simple object type (is it?) and it is not automatically serialized with the row object, in the attempt not to transfer too much data. Later, when it finds entry['data']
it decides it’s time to get the content of the data
column and does the round trip to fetch it.
I’m just guessing, I might be wrong in how the details work, but the concept should be close enough.
Not knowing and not having control over these details, together with a negative effect on performances, is why I never use row objects on the client side. I always create a dictionary with all and only the values I need and I send that to the client.
1 Like