How do I use portable classes with uplink? (updated)

The docs say to declare the portable class as a client side module, so it can be read by both client and server. @meredydd’s announcement also says PCs can be used in uplink code.

Problem is the uplink code cannot see the module so I cannot import it on the remote server, and therefore I cannot use it. So I recreate the PC module on the uplink server to get the uplink code to run.

When I call the uplink server function, I get this error when attempting to return a PC :

ImportError: No module named portable_classes

and the line number given is the client form with the server call.

Here is a test app :

https://anvil.works/build#clone:TKXY5PPLJQCFLWLS=AXILEBBZSFI2QLTVFFIYSEHL

and here is the uplink code :

import anvil.server
from portable_classes import Response

anvil.server.connect("redacted")

@anvil.server.callable
def remote_test():
  return Response(500, [], "test remote")

and here is the PC module copied straight from the IDE module :

import anvil.server

@anvil.server.portable_class
class Response:
  def __init__(self, status=999, data="", text=""):
    self._status = status
    self._data = data
    self._text = text
  
  @property
  def status(self):
    return self._status
  
  @property
  def data(self):
    return self._data
  
  @property
  def text(self):
    return self._text

Anvil uses the __module__ property of the portable class in order to know where to look in your app to reconstruct the object.

So for uplink code that means you could have your uplink structure match your app structure.

main.py # run the uplink script: from AppName.portable_classes import Response
AppName
   __init__.py
   portable_classes.py

Or alternatively - since in the clone link the object is sent from the server to the client (and not the other way around) - you could override the module property defined in your uplink script.
@anvil.server.portable_class
class Response:
    __module__ = "AppName.portable_classes" 
    # should match the location of the client module where this is defined

There might be a better way.

1 Like

That did the trick, thanks.

I’m a little out of my depth with this stuff :slight_smile:

edit -
the only downside of that is I can’t then share my PCs amongst apps, as it ties itself to an app name. Not an issue right now, but a limitation none the less.

Turns out there is a much better way.

@anvil.server.portable_class("_Response") #some unique name
class Response:
   ...

By default the portable class uses the __module__.__name__ to register the portable_class. But you can use the decorator with a name as an argument and register it with that unique name. So long as the portable class has been registered with the same name in your uplink script and in your app it should work.

I’m not sure this is documented.

2 Likes

I did this :

# Uplink code
@anvil.server.portable_class("_Response")
class Response:
  def __init__( ... etc

# Client module
@anvil.server.portable_class("_Response")
class Response:
  def __init__( ... etc

but got this :

SerializationError: No such serializable type: _Response

The error shows as being in the client form on the line that calls the uplink function.

edit - corrected the error message, typo.

Make sure it’s been registered - i.e. your client code needs to have imported the module that contains the portable class into the running app at some point prior to the server call.

#Client Code
from . import portable_classes

...

    anvil.server.call('my_uplink_func')
2 Likes

I had indeed commented out the import.

Less haste more speed …

Thanks @stucork , works now.

1 Like

I am having the exact same issue as @david.wylie was having. I think it could be clearer in the documentation for using Portable Classes with the Uplink.

Should we be using the Client Uplink or Server Uplink to have a Portable Class exist outside our app, initialized on a different machine (like personal computer)?

I have a sample of how I am implementing the Portable Class, but I don’t think its actually using the class I’ve defined locally with the Uplink. I have turned off the Uplink connection, and the Portable Class still gets invoked in my Anvil App when I run it.

My local file structure is setup with the module called uplink_module.py in a flat file directory. The uplink connection is created in another file called client_uplink.py (I have also tried with a server uplink - server_uplink.py). The Anvil app gives an import error if I try importing uplink_module.py instead of Portable_Classes.

uplink_module.py

import anvil.server

@anvil.server.portable_class('_Test')
class Test_Class():
  
  def __init__(self):
    self.say_hello()
    
  def say_hello(self):
    print("Hello, world from Uplink")

Just adding a little recipe that I use to the thread. I am accessing my Anvil App from a Jupyter Notebook and ultimately I won’t have the source code at the runtime to import, so this is what I’ve done in the Jupyter Notebook to use Portable Classes … One downside is you need to repeat the Model / Portable Class definitions, but the upside is that it works.

# models.py  ... Anvil Client Module

@anvil.server.portable_class("_User")
class User():

  def __init__(self, **kwarg):
    self.email = kwarg.get('email', '')
    self.id = kwarg.get('id', '')
     # ...

# server1.py  ... Anvil Server Code    
from models import User


@anvil.server.callable
def authorize_external_session(auth_user: User):  

  # Start the authorization flow,
  api_url = "external_service_API"
  params = {
      'url': url,
      'apikey': anvil.secrets.get_secret('my_api_key'),
      'username': auth_user.email
  }

  response = requests.get(api_url, params=params)
  #   ...


Jupyter Notebook

(in cloud, no local Anvil code from git clone, etc)

# Cell 1 
import os
import anvil.server
import anvil.tables as tables
import anvil.tables.query as q
from anvil.tables import app_tables 

# Server uplink key
anvil.server.connect(os.environ.get('uplink_key'))
# Cell 2  

# sad, but have to repeat (copy/paste) the model definition
@anvil.server.portable_class("_User")
class User():

  def __init__(self, **kwarg):
    self.email = kwarg.get('email', '')
    self.id = kwarg.get('id', '')
     # ...  

# Cell 3
# Since we can't do an import.. reload the modules
%load_ext autoreload
%autoreload 2

# create some portable classes here in the Notebook to pass to server module
rows = app_tables.users.search()
user_list =  [User(**dict(row), id=row.get_id()) for row in rows]   
# Cell 4 
anvil.server.call("authorize_external_session", user_list[0])

Obviously doesn’t need to be four cells.