How to import a server module both when running on Anvil and when running locally

I clone my apps to my pc so I can test the modules with classes that do not interact with the UI (because interactions with the UI happen in the forms).

The UI-free classes call server side functions to do their job, so everything works well.

I would like to test locally also the server functions, but sometimes, for example when there are dependencies, it is difficult to import a server side module in my local environment.

I was able to do the job with this solution, but it feels hacky.
Is there a better way?

import anvil.server
[...]
if __spec__ and 'workspace' in __spec__.origin:
    # running tests on pc
    CHECK_CREDENTIALS = False
else:
    # running on Anvil server
    from MyDependency import DependencyFunctionsServer
    CHECK_CREDENTIALS = True

@anvil.server.callable
def get_projects(search_text=''):
    if CHECK_CREDENTIALS and 'Internal User' not in DependencyFunctionsServer.user_permissions():
        ...

Here’s an idea: How about two distinct credential-checking modules?

  1. where the function(s) do all the intended checks. If that module needs to import other things, it does.
  2. where functions with the same names (and parameters) do no checking at all.

Your calling module chooses which one to import. Below that, the function calls are exactly the same.

I don’t understand…
Would I still need to import different stuff when running in Anvil and when running locally?
If yes, then my question is still there.
If no, then I don’t understand what you are suggesting.

An example might help. If I understand correctly, then what “feels hacky” is the inclusion of code like the above in multiple places in multiple modules, each of which is supposed to run both locally and on Anvil’s server. In short, it’s a DRY problem.

This is a quick bit of code, off the top of my head, so I might have some important details wrong.

Based on your example above, one could refactor as follows:

In a server-side module named CredentialChecker :

from MyDependency import DependencyFunctionsServer
def credentialCheckPassed():
    return 'Internal User' in DependencyFunctionsServer.user_permissions()

In the local-PC version of the same module:

def credentialCheckPassed():
    return True

In the main module:

import anvil.server
[...]
from CredentialChecker import credentialCheckPassed

@anvil.server.callable
def get_projects(search_text=''):
    if not credentialCheckPassed():
        ...

If it’s something else about the code, that “feels hacky”, then I’ve clearly missed the point.

What feels hacky is for me to dig in the globals() looking for something that makes a difference between running on Anvil server and locally, finding out that __spec__.origin seems to do the job, and using it to change the import logic. I went through it, it works, but… it just smells funky.

The if CHECK_CREDENTIALS check has been there for a while. It ain’t pretty, but it allows me to run server functions from the Server Console (the App Console is signed in, the Server Console is not). So, since it was there I used it… but, yes, that’s not pretty either.

Are you saying that I should:

  • clone the repository, which comes with CredentialChecker.py
  • edit my local copy of CredentialChecker.py, so it defines the non-importable dependencies just to make the main module happy
  • when I commit and push I need to remember not to check in the modified CredentialChecker.py

This 3rd point makes me worried a little. I might forget about it. Not a big deal, because the app wouldn’t run and I would fix it immediately, but…

I thought of playing games with PythonPath, then thought better of it.

The classic “another level of indirection” might solve part of the problem. Let’s rename the server version of the module as RealCredentialChecker, the PC version as DummyCredentialChecker. Then use generic module CredentialChecker to choose between them, as follows:

import anvil.server
[...]
if __spec__ and 'workspace' in __spec__.origin:
    # running tests on pc
    from DummyCredentialChecker import credentialCheckPassed
else:
    # running on Anvil server
    from RealCredentialChecker import credentialCheckPassed

This way, it’s exactly the same code on both PC and Server.

The code above is still “hacky” in your original sense. But at least the “hack” is contained where it can be clearly documented. The function credentialCheckPassed helps isolate the calling code from hacky details. And its name (you can surely pick a better one!) may help document the calling code.

1 Like