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:
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.