GithubHelp home page GithubHelp logo

benlawraus / anvil_works_design_test Goto Github PK

View Code? Open in Web Editor NEW
2.0 1.0 1.0 47 KB

To enable design and test of anvil.works apps using mock classes and sqlite database.

Shell 96.17% Python 3.83%
anvilworks

anvil_works_design_test's Introduction

SUMMARY

To assist with coding (with auto-complete) and testing of an anvil.works app within an IDE such as PyCharm.

An example of this is cached_lists_anvil_works.

Uses _anvil_designer and yaml2schema to generate a pseudo-front-end and a back-end on a local machine. After installation, no internet connection is required to run tests, as the database is replicated in sqlite.

Install

This is basically an empty project. Clone it using::

git clone https://github.com/benlawraus/anvil_works_design_test --recursive
mv anvil_works_design_test myProject
cd myProject
vi setup_project.zsh  # change the first line to your anvil.works app link
chmod +x setup_project.zsh
./setup_project.zsh

Before executing the script, change the first line in the script from:

myAnvilGit="ssh://[email protected]:2222/gobblygook.git"

to your actual anvil.works app link found from your anvil.works account.

After setup_project.zsh is finished::

source venv/bin/activate
python3 -m pytest tests

This will run a test to make sure your test user can log in and out.

Use

Once setup_project.zsh is finished, the app can be developed further within an IDE such as PyCharm. The front-end code is in client_code and the server code is in server_code. pytest code can be placed in a separate tests directory and run::

python3 -m pytest tests_project

Some example tests are in the anvil apps cached_lists_anvil_works and pyDALAnvilWorks.

When the local machine code passes the tests, run

    ./git_push_to_anvil_works.zsh

to sync changes to the online app.

If there is new code on the online app, run

    ./git_pull_from_anvil_works.zsh

to sync the local machine to the online app.

If there are changes to the online database schema, pull the app to the local machine (using the above script) and run

    ./yaml2schema.zsh

to update the local database schema.

Purpose

This project exists in order to use PyCharm (and other IDEs?) on anvil.works apps.

  • Use any local database while testing your anvil.works app.
  • Create and run tests using pytest. These tests would be for the python in client_side forms, as well as server_side python. No testing of javascript UI can be done here, although it is possible to execute the self.link_clicked(**event_args) of form's class.
  • Most importantly: use PyCharm with auto-complete and GitHub co-pilot.

Notes

Directory Structure

  • venv (created by setup_project.zsh) Is the local python virtual environment.
  • anvil (a set of mock classes of anvil module )
  • _anvil_designer (converts form_template.yaml` into mock classes )
  • client_code (git-cloned from anvil.works)
  • server_code (git-cloned from anvil.works)
  • tests
    • database (your sqlite and pydal files to run your database on your laptop)
    • pydal_def.py # generated from anvil.yaml using yaml2schema
  • tests_projects
    • test_00_yaml.py (your own pytest)
  • anvil.yaml (git-cloned from anvil.works)
  • backup This is a copy of client_code and server_code that is saved here before pulling the app from anvil.works.

Scripts Action

  • setup_project.zsh (creates venv, clones submodules) Executed only once to initialize project structure.
  • yaml2schema.zsh (converts anvil.yaml to tests/pydal_def.py, the database schema) This script needs to be run after any changes to the database schema.
  • git_push_to_anvil_works.zsh (pushes your changes to anvil.works) Needs to run to sync local changes to the app server.
  • git_pull_from_anvil_works.zsh (pulls your changes from anvil.works) Needs to be run to sync local machine to changes on app server.
  • _anvil_designer/generate_files.py (converts form_template.yaml to mock client_code classes in _anvil_designer.py) Is executed from git_pull_from_anvil_works.
  • _anvil_designer/generate_apptable.py (generates dummy classes in AppTables from anvil.yaml to allow auto-complete of database tables). Is executed from yaml2schema.

Database

  1. Run ./yaml2schema.zsh after changes to database schema of the online anvil app. Changes are reflected in anvil.yaml which is pulled from the online app using the script git_pull_from_anvil_works.zsh.
  2. anvil/tables/AppTables.py is a mock module of the table classes. It's sole purpose is for auto-complete of database tables.
  3. It is possible to download the anvil.works database into the local machine's sqlite database. A csv file would be exported from the online app and imported into sqlite using pyDal.
  4. Other databases such as postgresql can be used. The pydal module is used to connect to the database. Refer to the pyDal documentation for more information.

server_code

The anvil.yaml file is used to generate the database and the AppTable class. The AppTable class is needed to utilize auto-complete in the IDE for table names. The database and AppTable needs to be re-generated after every change to the database on anvil.works otherwise local code and online code are not synced. The sync will delete the local test database when it is re-schemed. yaml2schema.zsh automates this action.

(Note:anvil/tables/AppTables.py is generated by:

python -m _anvil_designer.generate_apptable

included in yaml2schema.zsh.)

client_code

For client code tests, _anvil_designer.py needs to be generated in the form directory. Every form needs one. _anvil_designer generates the mock classes of the forms contained in each _anvil_designer.py so that useful tests can be written for client code and auto-complete is functioning on form components. To generate the _anvil_designer.py files:

python -m _anvil_designer.generate_files

This is included in git_pull_from_anvil_works.zsh.

In case it is needed within a test:

from _anvil_designer.generate_files import yaml2class
class TestYaml2Class:
    def test_init(self):
        yaml2classes()

The entire anvil docs have been converted into dummy classes and functions in the repo anvil. If the IDE does not auto-complete, make sure the dummy class or function has an instruction to be imported. These classes and functions are in the anvil directory.

A form is then a child class of a Template class imported by:

from ._anvil_designer import Form1Template

A Form init would look like::

class Form1(Form1Template):
    def __init__(self, **properties):
        # Set Form properties and Data Bindings.
        self.init_components(**properties)
        self.drop_down.items = ('up','down','sideways')

When running python on a local machine, the attributes of Form1 are initialized in the self.init_components(), so::

self.drop_down.items = ('up','down','sideways')

has to be AFTER the call to init_components().

Tests using pytest

Tests are run using pytest.

User Login/Logout

When running tests, the user may need to be logged in. Tests may fail when run in parallel (pytest) but successfully complete when run individually. To prevent this, save a unique user in the db for each test and log this user in using::

anvil.users.force_login(user)

Example:

import anvil.users
from _anvil_designer.set_up_user import new_user_in_db
from anvil.tables import app_tables
from tests import pydal_def as mydal

class TestContactForm:
  def test_save_contact(self):
    # generate demo user
    mydal.define_tables_of_db()                         # define tables
    user = new_user_in_db()                             # create user
    anvil.users.force_login(user)                       # log in user
    from client_code.ContactForm import ContactForm     # import the form
    # generate form
    c_form = ContactForm()                              # create form

This repo uses pytest's env to mark the user. At the end of the test, use::

anvil.users.logout()

See test_HomeForm.py for another example test.

Type Checking

It is possible to type check client code using Python 2 style comments and PyCharm. See PyCharm type checking and PEP 484.

There is a anvil.server.context object that can be used with types such as Union and Any. This repo sets:

anvil.server.context.type = "laptop"

so in your client code:

if anvil.server.context.type == "laptop":  # for type checking
    from typing import Union
    from .portable_contact import Phone, Email, Location

texts_to_check = dict()  # type: dict[str, Union[Phone,Email,Location]]

Anvil-Extras

Anvil-Extras

Some of its code can be used with this repo without change, such as messaging.py. What has been used successfully, is to copy messaging.py from anvil_extras and place in the client_code directory. Then the publish/subscribe functionality can be used on the local computer and on anvil.works without further modification.

However, others need to be modified. This is the reason it was forked and copied into its own repo. The cached_lists_anvil_works repo uses a modified storage.py from anvil_extras to imitate browser indexed_DB.

Paths

If server-code modules cannot see client_code modules or vice-versa, the IDE may need to include these in their paths. To do this in PyCharm:

PyCharm Paths

Updating Rows

anvil.works allows you update your database using:

row['name']="Rex Eagle"

This is allowed in this wrapper, with the allowance that no sqlite row will be updated, only the object row will be updated. To update the database row, you have to use row.update()

Using dict(row)

The dict() function needed to be overwritten in order for it to work with pydal row objects. So if dict() is used, also need to add::

if anvil.server.context.type == 'laptop':
    from anvil import dict

Circular Referencing Tables

yaml2schema cannot handle two tables referencing each-other. For example::

    child_table['parent_table']  <-> parent_table['child_table']

Pytest Fixtures and User login

When running a test, this project uses the process id (PID) of the test to keep track of the user that is logged in. Logging a user in and out using PyTest fixtures may cause the user log in process to use a different PID than the test, so the test may act as if there is no user logged in. To prevent this, log in the user within the test and not within a fixture.

Errors during from client_code.HomeForm import HomeForm

During import, python may run the init of every class. If the class of a form uses an anvil.users.get_user(), then an error will occur because there is no connection to the database. To overcome this, the import has to occur after the users tables has been initialized. An example is from test_HomeForm

Package and Module Forms

In the anvil.works, there are package forms and module forms. This repo can only handle package forms.

Compatibility

This project was developed and tested only on OSX with PyCharm. To run on other systems, the scripts need to be translated for your particular system. ChatGPT can offer a good starting point.

anvil_works_design_test's People

Contributors

benlawraus avatar

Stargazers

Patryk Bański avatar  avatar

Watchers

 avatar

Forkers

nickantonaccio

anvil_works_design_test's Issues

Error running setup_project.zsh

Hey there!

Had a crash here:

make scripts executable ..
Copy server and client files (running git_pull_from_anvil_works.zsh..
No arguments supplied. Using:
     /home/junior/anvil/uplink/my_project/AnvilWorksApp
     /home/junior/anvil/uplink/my_project
Copying laptop files into backup directory before syncing.
cp: cannot stat '/home/junior/anvil/uplink/my_project/server_code': No such file or directory
cp: cannot stat '/home/junior/anvil/uplink/my_project/client_code': No such file or directory
Git pull the anvil.works app..
Enter passphrase for key '/home/junior/.ssh/id_rsa': 
From ssh://anvil.works:2222/AWWAZ7OBMZSOCBTV
 * branch            master     -> FETCH_HEAD
Already up to date.
Copy anvil app code to project directories..
building file list ... done
created directory /home/junior/anvil/uplink/my_project/server_code
./
AnexosModule.py
ClientesModule.py
EmpresasModule.py
ProdutosModule.py
SolicitacoesModule.py
StatusModule.py
UserModule.py
requirements.txt
Utils/
Utils/__init__.py

sent 15.152 bytes  received 263 bytes  30.830,00 bytes/sec
total size is 14.413  speedup is 0,93
Regenerating _anvil_designer.py files in client_code..
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_files.py", line 28, in <module>
    if yaml2classes():
       ^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_files.py", line 19, in yaml2classes
    file_string = yaml2definition(parsed, form_name)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 346, in yaml2definition
    derive_dict(parsed, catalog, form_name, import_list, form_name)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 267, in derive_dict
    derive_dict(component, catalog, parent, import_list, form_name)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 267, in derive_dict
    derive_dict(component, catalog, parent, import_list, form_name)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 277, in derive_dict
    catalog[name] = lowest_level_component(value, parent, import_list, form_name)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 227, in lowest_level_component
    attrs = add_properties(value, parent)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 150, in add_properties
    validate_yaml(value, 'properties')
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 99, in validate_yaml
    validate_yaml(value[sequence_key], key)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 103, in validate_yaml
    value.revalidate(sy.EmptyList())
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/representation.py", line 109, in revalidate
    result = schema(self._chunk)
             ^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/scalar.py", line 27, in __call__
    chunk.expect_scalar(self.rule_description)
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/yamllocation.py", line 125, in expect_scalar
    self.expecting_but_found(
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/yamllocation.py", line 44, in expecting_but_found
    raise YAMLValidationError(
strictyaml.exceptions.YAMLValidationError: when expecting a emptylist
  in "<unicode string>", line 11, column 1:
        properties:
    ^ (line: 11)
found a mapping
  in "<unicode string>", line 34, column 1:
          icon: fa:paperclip
    ^ (line: 34)
git pull completed.
If the database schema has changed, run yaml2schema.zsh
Generate all the _anvil_designer.py files for every form.
Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_files.py", line 28, in <module>
    if yaml2classes():
       ^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_files.py", line 19, in yaml2classes
    file_string = yaml2definition(parsed, form_name)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 346, in yaml2definition
    derive_dict(parsed, catalog, form_name, import_list, form_name)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 267, in derive_dict
    derive_dict(component, catalog, parent, import_list, form_name)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 267, in derive_dict
    derive_dict(component, catalog, parent, import_list, form_name)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 277, in derive_dict
    catalog[name] = lowest_level_component(value, parent, import_list, form_name)
                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 227, in lowest_level_component
    attrs = add_properties(value, parent)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 150, in add_properties
    validate_yaml(value, 'properties')
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 99, in validate_yaml
    validate_yaml(value[sequence_key], key)
  File "/home/junior/anvil/uplink/my_project/_anvil_designer/generate_class.py", line 103, in validate_yaml
    value.revalidate(sy.EmptyList())
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/representation.py", line 109, in revalidate
    result = schema(self._chunk)
             ^^^^^^^^^^^^^^^^^^^
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/scalar.py", line 27, in __call__
    chunk.expect_scalar(self.rule_description)
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/yamllocation.py", line 125, in expect_scalar
    self.expecting_but_found(
  File "/home/junior/anvil/uplink/my_project/venv/lib/python3.11/site-packages/strictyaml/yamllocation.py", line 44, in expecting_but_found
    raise YAMLValidationError(
strictyaml.exceptions.YAMLValidationError: when expecting a emptylist
  in "<unicode string>", line 11, column 1:
        properties:
    ^ (line: 11)
found a mapping
  in "<unicode string>", line 34, column 1:
          icon: fa:paperclip
    ^ (line: 34)
Crashed while regenerating the _anvil_designer.py files.

My form_template.yaml:

container:
  type: ColumnPanel
  properties: {col_widths: '{"SWPKZM":50}'}
components:
- type: ColumnPanel
  properties: {col_widths: '{"EZXAFF":60,"XWBHIZ":15}'}
  name: column_panel_1
  layout_properties: {grid_position: 'QFSUQZ,SWPKZM'}
  components:
  - type: FileLoader
    properties:
      role: null
      show_state: true
      align: center
      tooltip: ''
      border: ''
      file_types: .png,.jpeg,.jpg
      enabled: true
      files: []
      foreground: ''
      visible: true
      text: Anexo
      font_size: null
      font: ''
      spacing_above: small
      icon_align: left
      spacing_below: small
      italic: false
      file: null
      background: ''
      bold: false
      underline: false
      multiple: false
      icon: fa:paperclip
    name: fl_anexo
    layout_properties:
      col_widths: {}
      grid_position: XOJOYE,XWBHIZ
  - type: TextBox
    properties: {}
    name: inp_comentario
    layout_properties:
      col_widths: {}
      grid_position: XOJOYE,EZXAFF
    event_bindings: {pressed_enter: inp_comentario_pressed_enter}
is_package: true

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.