Using PyCharm for development and testing

No. PyCharm can’t replace the Anvil IDE.
For simple apps, using the Anvil IDE is the best and only way.

For complex apps, the IDE must still be used for the front end, but PyCharm can help developing and testing server modules and HTTP endpoints / server callable functions.

The setup described here allows to get an app with dependencies to work both in the Anvil server and in PyCharm, and PyCharm to recognize all the definitions. Well, almost. Ctrl+Click on a name seems to always work correctly in the main app, even when the Ctrl+Click is done on a member of a dependency app, but some names in the editor, especially on the dependency apps, may have the red wiggle.

This is not necessarily the right or best approach. It’s a way that works for me. I wrote this post while looking at some old apps, testing with 3 new test apps and applying what I was writing to another set of apps where one depends on another that wasn’t designed to be a dependency. All the old apps were working both in Anvil and locally, no two apps had the same configuration. I struggled through this kind of setup every time and I created a barely working mess every time.

From now on I will try to consistently use this setup. I am writing it down here for my future self, for anyone that may find it useful, and for anyone that may have something to add or to correct. Please let me know if there is a better way.

App folder and subfolder with same name

All apps must be cloned to a folder child of a folder with the same name. The folder structure for App2 depending on App1 must be something like this:

+ repositories
|---+ App1
|   |---+ App1
|       |--- __init__.py
|       |--- anvil.yaml
|       |--- anvil_editor.yaml
|       |---+ client_code
|       |   |---+ Form1
|       |   |   |--- __init__.py
|       |   |   |--- form_template.yaml
|       |   |--- Module1.py
|       |---+ server_code
|       |   |--- ServerModule1.py
|       |---+ Tests
|       |   |--- tests.py
|       |---+ theme
|           |--- [...]
|---+ App2
|   |---+ App2
|       |--- __init__.py
|     [ ... same as above ... ]

Modify __path__ in __init__.py

Anvil changes __path__ to simplify the import from the folders server_code and client_code, but this change confuses PyCharm. A little change to that file seems to mitigate the problem.

Modify the __init__.py of every app like this:

# from this:
__path__ = [__path__[0] + "/server_code", __path__[0] + "/client_code"]

# to this:
__path__ = [__path__[0] + "/server_code", __path__[0] + "/client_code", __path__[0]]

Add each dependency app as a content root

In PyCharm go to the project structure page and add the grandparent folder of the main app and of each dependency app as a content root, then select the top level folder of each app and mark it as a source folder.

Notice in the snapshot the top level folder of App1 is blue because it has been marked as source folder:

Add Git roots

Add the folder containing the git repository folder for each dependency to the Directory Mappings:

This allows to see all the local changes and all the repositories as if they were one single large repository, or hide some of them and show only others:
image

Relative vs absolute imports

There are 3 ways for App1 to import one server module from another server module: relative, absolute and absolute with app name. Use the third one if you want an app to work standalone, as a dependency and as a dependency in PyCharm. This table summarizes what type of import works when the import is executed by the running app, by one of its dependencies or while running locally.

Type Example App Dependency Locally
Relative from .ServerModule2 import something Yes No No
Absolute from ServerModule2 import something Yes Yes No
With app name from App1.ServerModule2 import something Yes Yes Yes

Consistency is important!
If one module has import ServerModule1 and another module has import App2.ServerModule1, they are considered two different imports and the same module is imported twice.

Importing server or client modules from dependencies

There must be two lines to import server modules, one so it works and one so PyCharm knows about it, otherwise the code would run, but the autocompletion wouldn’t work. PyCharm would get confused by the change in __path__ made by __init__.py and would not recognize the code in the server modules.

We take advantage of the fact that PyCharm tries to interpret all the imports in all the try / except blocks. In the try block we use some import statements because they work, but PyCharm doesn’t understand them, and in the except block we use other import statements that will never be executed, they are there so PyCharm understands them and the autocompletion works.

The first statement in the except block is raise, so any problem during the import is exposed. PyCharm understands that the first import after raise will never be executed and shows an “unreachable code” warning, but will still process the import and the autocompletion will work. Adding a line with the comment # noinspection PyUnreachableCode after the raise will get rid of the warning.

I was able to get the autocompletion to work with one import statement only, by marking server_code and client_code as source folders on all the apps, including the dependencies, but this creates confusion when two apps have server modules with the same name. I like the two statement solution, even if it means changing the source code just to make PyCharm happy.

For example, with App2 depending on App1:

try:
    # this works in Anvil and in PyCharm
    import App1.ServerModule1 as App1ServerModule1

except ImportError:
    # this enables autocompletion in PyCharm
    raise
    # noinspection PyUnreachableCode
    import App1.server_code.ServerModule1 as App1ServerModule1

Importing forms

I don’t do much front side development on PyCharm, but I like to be able to ctrl+click on a function or class name inside an event handler and immediately jump to the definition.

The try / except double import trick can be used also inside forms. Here is how to import the form Form3, subform of Home:

try:
    # this works in Anvil and in PyCharm
    from App2.Home.Form3 import Form3

except ImportError:
    # this helps auto-completion in PyCharm
    raise
    # noinspection PyUnreachableCode
    from App2.Home.client_code.Form3 import Form3

Importing other modules

The double import trick must be used when importing modules inside the server_code and client_code folders from modules that can run in Anvil. If the module that imports is not going to run on the server, for example it is located in the Tests folder, then there is no point in making the double import. Something simpler like this will work:

from App1.server_code.ServerModule1 import func1
from App1.Tests.my_test import func2

This consideration does not apply to modules that are loaded also by the app code. For example, if the test script loads func1 as described above, and then there are other imports that trigger the import of the same App1.ServerModule1, the module will be loaded twice, once with and once without server_code in the path name.

Debugging HTTP endpoints and server callables

Create a Tests/test_callables.py file and import all the server modules.

Running Tests/test_callables.py in debug mode inside PyCharm will execute all the server callables in the local computer, stopping at the breakpoints and allowing to debug code that would otherwise run on the server.

It’s really cool: you run the app, and the callables run on the Anvil server. You start the uplink script on your computer, and all of a sudden all the callables run on your computer. You stop the uplink script, and the callables are back running on the server. Do not launch and forget that script running overnight on a production app, otherwise all the production calls will be executed on your computer (been there, done that).

Here is an example of test_callables.py:

# this file imports the server modules and registers their callables, so
# http endpoints or server calls are executed locally and can be debugged

import anvil

try:
    # this works in Anvil and in PyCharm
    from App1 import ServerModule1 as App1ServerModule1
    from App2 import ServerModule1 as App2ServerModule1

except ImportError:
    # this helps autocompletion in PyCharm
    raise
    # noinspection PyUnreachableCode
    import App1.server_code.ServerModule1 as App1ServerModule1
    import App2.server_code.ServerModule1 as App2ServerModule1

if __name__ == '__main__':
    anvil.server.connect('<UPLINK KEY>')
    anvil.server.wait_forever()

Order of imports

It is easy to control the order modules are imported when running locally, because the script that is executed decides what’s imported first.

When running in Anvil instead, the whole app is loaded, not just one script. All the modules are imported in alphabetical order, so it may be useful to create a module called AAAFirstImportedModule and use it to import all the modules in the correct order.

For example, this ensures that Helpers is loaded before AllMyStuff:

# AAAFirstImportedModule.py
try:
    # this works in Anvil and in PyCharm
    from App1.Helpers import *
    from App1.AllMyStuff import *

except ImportError:
    # this helps autocompletion in PyCharm
    raise
    # noinspection PyUnreachableCode
    from App1.Helpers import *
    from App1.AllMyStuff import *

If an app uses the AAAFirstImportedModule trick, a test script can simply do from App2.server_code.AAAFirstImportedModule import * and get all the imports done in one shot, in the same order as in the production app.

3 Likes