Hi @owen.campbell,
I’m having to infer in between the blanks here, but it sounds to me like what you want is a server function that will instantiate an arbitrary(?) class nominated by client code(!). (For clarity, if it weren’t client code, the answer would be easy - if you’re already on the server, you don’t have to anvil.server.call()
into your dependency, just import the module and call a function, passing in a class as an argument if you want.)
Now, my first reaction is alarm, because allowing the client to control what class you instantiate is a really scary idea. Untrusted client code could get up to all sorts of mischief, and even if you believe that all the classes in your code are safe to instantiate in arbitrary ways, there is an entire class of vulnerabilities (they’re generally called “deseralisation vulnerabilities”) proving that this belief is usually wrong - if an attacker can cause your trusted code to instantiate a class of its choice, you’re probably hosed.
Fortunately, the answer to this is the same thing I would have proposed anyway, which is to maintain an “allow-list”. Maintain a set of classes that your server function is allowed to instantiate, and allow the client to pick only from that list. Something like:
ALLOWED_CLASSES = {'Widget': Module1.Widget, 'Badger': Animals.Badger}
@anvil.server.callable
def instantiator(cls_name, arg):
cls = ALLOWED_CLASSES[cls_name]
return cls(arg)
If the instantiator
function is defined in a dependency, no problem! Your main app’s code can add classes to that dictionary, like so:
# In my main app
import MyDependency.Instantiation
class Wombat:
def __init__(self, x):
# This code must be very, very careful because it can be called by
# untrusted code with an arbitrary argument
MyDependency.Instantiation.ALLOWED_CLASSES["Wombat"] = Wombat
This code is now safe, because only nominated classes can be instantiated, but flexible, because your main app can add additional classes to that list.
If you want to be even fancier, you could make it a decorator. For example:
# In the dependency:
ALLOWED_CLASSES = {'Widget': Module1.Widget, 'Badger': Animals.Badger}
def untrusted_code_can_instantiate_me(cls):
"""Use this function as a decorator for classes that are safe for
untrusted code to instantiate."""
ALLOWED_CLASSES[cls.__name__] = cls
return cls
@anvil.server.callable
def instantiator(cls_name, arg):
# ..etc..
and then:
# in the main app:
from MyDependency.Instantiation import untrusted_code_can_instantiate_me
@untrusted_code_can_instantiate_me
class Wombat:
def __init__(self, x):
# This code must be very, very careful because it can be called by
# untrusted code with an arbitrary argument