Larger Application Best Practices

For larger applications, it’s a good idea to set your project up with a safe package structure. The recommended package/module structure looks like this:

|-tests/
|
|-entry_points/
| |-run_app/s.py
|
|-apps
| |-static/
| |-widgets/
| |-gui/
| |-app1/
| | |-app_def.py
| | |-steps.py
|
|-tasks/
|
|-models/
| |-orm/
| |-external_orm/
| |-parameters/
| |-data_extraction/
| |-db_management/
| | |-alembic/
| | | |-env/
| | | |-versions/
|
|-utils/
|
|-bin/
|
|-data/

Fitting It Together

The components of the app are entry_points, apps, tasks, models and utils. There is also space in the structure for binary files, bin and data. An important rule exists to minimise risk of circular imports: each level of the diagram below may depend on any of the levels below it (e.g. apps can import from tasks and models), but not from those above it (e.g. models should not import from tasks).

../_images/tropofy_high_level_structure.png

Models

models defines all the tables that your app will have, as well as providing a public data_extraction interface for commonly used queries (for example, many widgets in an app may use the same query). If you are using a database management package such as alembic, your alembic environment setup and versions will exist here as well.

Note

The public data_extraction/ modules may import from the orm model modules, and not the other way around. Modules defined in /orm should, without exception, not import from any other module in your application. If a database query requires more than one model, it should reside within data_extraction/ and not on a model class or in a module that defines multiple model classes.

Tasks

tasks is where the guts of the logic is. Any business logic, calls to external subprocesses, updating of table data, etc. should exist here. Tasks should have no knowledge of who called them (be it a widget in an application or some other entry point). Generally, they make use of models directly, and via the public models/data_extraction/ modules to retrieve data, and use this information to perform logic and update relevant tables. The standard public interface to tasks should generally be a top-level function which accepts a data_set argument along with any other information from the entry point above it.

Note

app_session should not be passed as an argument to a task. There should be no requirement for a task to know about an ‘application’ or other entry point which is calling it. Instead, it should only take data_set and any other relevant arguments (e.g. values of app parameters relevant to the task)

Apps

This is where your tropofy apps are defined. /static holds any custom javascript/templates you are using. /widgets is where each and every custom widget class is defined. parameters/ is where all application-level parameters are defined. /gui implements any common Step or Stepgroup instances used across multiple applications. The app_def.py modules each implement a single Tropofy Application object. steps.py is an optional module to help with particularly large applications which don’t want to clutter the app definition with gui details.

Note

parameters are owned directly by the application, not by a ParameterForm widget. Parameters can be grouped together using the ParameterGroup object, which can then easily be used by ParameterForm widgets to define the parameters shown to the user.

Entry Points

This optional sub-package defines any other python processes which may need to make use of tasks and models. You may, for example, have a python process running in the background continually archiving data.

Data

No python modules should exist here. Only static and/or example data (Excel, csv, or zip files) should exist here.

Bin

Location for engine executables to reside. It is expected that no python files would reside in here, so importing from here is irrelevant.

Utils

Optional subpackage with general helper-functions. Modules in this package have no knowledge of models, tasks, apps or entry points, but may be used by any of these.

Other Notes

Widgets should generally have little or no logic residing on them. For the most part, widgets should either retrieve data from models/data_extraction or perform tasks which update data via the tasks sub-package.