- Python 2.7 (suggestion: on OSX, use the Hombebrew installation and follow the brew prompt's directions to overwrite links)
- gcloud sdk (also see)
- virtualenvwrapper (also see)
- Node (>= v4) (suggestion: on OSX, use the Homebrew installation)
- react-devtools
- git clone this repo and then
cd
into it - make a virtualenv and ensure it's active. E.g.,
mkvirtualenv your-virtualenv-name -a .
pip install --upgrade pip setuptools
add2virtualenv ./gae ./gae/__app_env__
make rehydrate
workon your-virtualenv-name
This will activate the project's virtualenv and cd
you into its root directory.
honcho start
This is at the heart of the devlopment process. It will run the Google App Engine and Webpack development servers, as well as the Karma and Pytest test runners, both in watch mode. You will have a live instance of the application and will see the logging output of each of the processes in the terminal, each process having its own color. Use this development mode to implement new features in your app; the changes will be live reloaded as the app runs and cause relevant tests to re-run.
Furthermore, if you'd like to interact with the app directly, you can visit the /_console
path in your browser (i.e., localhost:8080/_console
). You can also instert a werkzeug_debugger()
call in the code to interact with the app at a specific point in execution, and then when this code is activated by an HTTP request from the browser client, an interactive Werkzeug debugger stack trace will open in the browser.
REACT_VIS=true honcho start gae webpack
The same as the previous command with the addition of setting the REACT_VIS
environment variable. This will transform your React components with react-transform-render-visualizer. Then when you interact with your app in the browser, your React components will show where, why, and how often they re-render. You can use this information to find bottlenecks or hogs in your app's UI that could possibly use some refactoring and optimization.
honcho start gae pytest
If you're working solely on the back end and want to spin up only the relevant processes (i.e., the GAE dev server and Pytest test runner), you can run this command. You can interact with the application via a client like Postman. You can also work interactively with the application via the GAE dev server's console or via localhost:8080/_console
.
dev_appserver.py gae/ --show_mail_body True --allow_skipped_files True
If you'd like to interact with the application via the command line, you'll need to run the application directly, instead of through honcho
. You can activate the Python debugger by inserting this line import pdb; pdb.set_trace();
in the code and calling an API endpoint that activates the line. When the debugger is active, you'll find the pdb
prompt in the terminal running the application. See Python debuggin with pdb.
make coverage
This command will run the full test suites for the browser client and the back end, both in code-coverage mode. When each of the two coverage reports are done, they'll open in your default web browser.
make check
Run linting and testing for both the GAE application and the browser client.
This command will lint and test the browser client and back end code. If no linting problems are encountered and all tests pass, the process will exit without an error.
py.test
This will perform a one-off test run of the GAE/Flask application. (NB: local config for pytest
is in setup.cfg
.)
npm test
This will perform a one-off test run of the browser client. (NB: config for this command is in package.json
.)
bpython
andptpython
These are both useful Python shells. When you're using one of them, you may want to run the following import
statement:
import __test__.fix_sys_path
(in Python file or shell)
This is used in the tests to ensure all google.appengine.*
packages are available for import, in addition to all GAE-bundled third-party packages. There will be no live application as there is with the dev console (see notes under honcho start gae pytest
above), so functionality that relies on a service backing it (e.g., many google.appengine.nbd
functions rely on a datastore connection) will not be available. Nevertheless, it's still useful to have these packages available for import because you can explore them easily thanks to the autocomplete feature of the Python shells listed above. Futhermore, you can create a temporary fake application to play with, using testbed
and a test app; see the testbed
fixture in gae/__test__/conftest.py
and the app
fixture in gae/__test__/test_app.py
for examples of how to set up a temporary test app.
py.test --pep8 gae/__test__/ gae/app gae/config/
Use this command occasionally to check for style issues. pep8
is very strict, and it's fine not to try to correct all the errors it raises.
- debug and profiling development modes
In gae/config/development.py
, the DEBUG
and PROFILE
feature flags control whether the development app runs in debug and profiling mode, respectively. While the app is running, you can toggle these flags between 1
and 0
(meaning "on" and "off," respectively). When you toggle these flags (or make any changes to the app), the GAE dev server's live reloading will ensure your app's config is up to date; there's no need to restart the server for your config changes to take effect.
In debug mode, any uncaught errors will open a Werkzeug interactive stack trace in the browser, allowing you to execute code against the app and investigate errors. In profiling mode, for each HTTP request, the app will log the slowest function calls involved in its processing the request-response cycle.
Perf
in browser's dev console
You can profile your React components by running the app in dev mode, opening it in the browser, and using the global Perf
object from the browser's dev console. The Perf
object is attached to the window
object by the app, and it allows you gather different profiling statistics on your React components. Here's an example usage from the browser's dev console that could be very useful:
Perf.start()
// perform some user interactions in the app...
Perf.stop()
Perf.printWasted()
// view table of the time spent, and where, on unnecessary re-renders
make build && honcho start gae
This will run the app locally with a production build of the browser client. The make build
part will also write stats about the Webpack bundle's build to __stats__/webpack.json
, relative to the project root. Use the webpack.json
file along with Webpack's analyse tool to see how various libraries and modules contribute to your production bundle's size.
After updating or adding npm packages, run npm run test:coverage
in addition to running the app and verifying things still work as expected. Then lock down the specific versions of the npm packages you're using with this command:
npm shrinkwrap --dev
This will update npm-shrinkwrap.json
; npm install
uses this file by default, which will ensure you're using the exact same version of third-parth npm packages across environments. (NB: if you're familiar with Ruby on Rails development with Bundler, you can think of npm-shrinkwrap.json
as being like Gemfile.lock
.)
make pip-app
ormake pip-dev
To add a new dependency to your project, add <package name>==<version number>
to requirements.app.txt
or requirements.dev.txt
, and then run the corresponding command above to install the dependency. requirements.app.txt
is used to install your app's dependency in gae/__app_env__
, which is deployed with your app to the GAE servers. requirements.dev.txt
is used to install all other dependencies (e.g., pytest
), which are only needed for development and are installed in virtualenvwrapper
's default manner.
pipdeptree
This command will produce a visualization of your pip-installed packages, which is useful for maintaining the requirements.*.txt
files. All of the package names that align to the left of the terminal window comprise the minimal set of names for your requirements.dev.txt
and requirements.app.txt
files. If there are any sub-dependencies whose version numbers you'd like to pin, add them to the the relevant requirements files as well.
Installing new dependencies frequently comes with changes, sometimes radical, to how the development process or the app itself works. A fairly quick and comprehensive way to verify everything's still working as expected is to follow these steps:
- Run
make coverage
and verify all tests and coverage reports run successfully. - Run
make clean && honcho start gae webpack
, open the app in the browser with the devtools console open, and interact with a selection of features/pages in the app. Look at the browser's devtools console as well as the log output in the terminal you've used to run the above command and verify no unexpected errors occur. - Run
make build && honcho start gae
and follow the same steps outlined in the point above. Now you're verifying that the build version of the app works with no unexpected errors.
Make sure you have a GAE account and have created the project under your account (details).
Also make sure you've replaced "your-app-id-here" with your project's ID under the application
field in gae/app.yaml
and in the deploy
field of the Makefile
.
Then use this command to build and deploy your project:
make deploy
This will build and deploy your app. (NB: the make deploy
command runs make check
as a dependency before the build and deploy steps are run.)
Once the app is deployed you can use the GAE console (or this alternative) and the remote API to manage and interact with the deployed application.
Sharing mutable data among your components is bad only if the data is actually mutated. And it's worse if it's mutated without your knowing! Using deepFreeze
in your tests will let you know if any mutations happen in your app's runtime.
The browser client's tests make extensive use of deepFreeze
. This means that any unexpected mutative behavior in the app will throw an error during test time, warning you of this undesirable behavior. Continuing to write tests like this as your app develops will protect you from accidentally introducing undesirable behavior in your app (maybe passing your state as the first argument to Object.assign
).
This has the added benefit of freeing you from having to always use ImmutableJS structures, or some other immutable structures, just for the sake of implementing an app that treats data as immutable. Then you're free to choose ImmutableJS only when there's a clear performance or convenience benefit. Sometimes it's convenient just to use plain JS objects for read-only state that will not be updated during the app's life.
When importing this class, I bind it to the name "Component" rather than "PureComponent"; this is to help eslint detect React components in the code and enforce important lint rules.
- Minimal actions and state
Actions carry just enough information for the store to generate the right state; the store carries just enough state for the React components to provide the right UI. Any state that can be computed from other pieces of state in the store should be taken out of the store and left implicit; this data can be computed later by containers or components of the app, using the explicit state of the store. Even if this implicit data is costly to compute and the same data may need to be recomputed several times throughout the life of the app, it's better left implicit; you can use pure-rendering components and memoized pure functions down the line to compute the data. This provides a good solution: your app is kept simple by managing the fewest pieces of state as possible, and your app is kept performant by using memoized pure functions and pure-rendering components to calculate and render implicit state the fewest number of times as possible for the UI.
Using Flask-RESTful, the order in which callbacks and decorators are invoked in a request-response cycle is roughly this:
Incoming request:
- app
before_request
- blueprint
before_app_request
- blueprint
before_request
- api decorators, rightmost to leftmost
- resource decorators, rightmost to leftmost
- view fn decorators, top to bottom
Outgoing response:
- view fn decorators, bottom to top
- resource decorators, leftmost to rightmost
- api decorators, leftmost to rightmost
after_this_request
- blueprint
after_request
- blueprint
after_app_request
- app
after_request
After response is sent:
- app teardown_request
That's the rough idea. However, for peer callbacks, the order of invocation depends on when the peers are registered with the app.
For the peers app before_request
and all blueprint before_app_request
s, the order in which they're invoked for an incoming request is the order in which they're registered with the app.
For the peers app after_request
and all blueprint after_app_request
s, the order in which they're invoked for an outgoing response is the reverse of the order in which they're registered with the app.
This information about the sequencing of peers is important. For maintainability, these high-level callbacks should depend as little as possible on their relative sequencing. However, for decorators, it's much easier to maintain sequential dependencies since there sequence is explicit in their colocation in the code and, possibly, their functional contracts.