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.
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 ... ]
__path__ to simplify the import from the folders
client_code, but this change confuses PyCharm. A little change to that file seems to mitigate the problem.
__init__.py of every app like this:
# from this: __path__ = [__path__ + "/server_code", __path__ + "/client_code"] # to this: __path__ = [__path__ + "/server_code", __path__ + "/client_code", __path__]
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 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:
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.
|With app name||
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.
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
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
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
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.
try / except double import trick can be used also inside forms. Here is how to import the form
Form3, subform of
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
The double import trick must be used when importing modules inside the
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.
Tests/test_callables.py file and import all the server modules.
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
# 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()
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
# 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.