Skip to main content

The gnarliest gear in the world 🤙

Project description

# Gnar Gear: Gnarly Python Apps

[![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
[![codecov](https://codecov.io/gl/gnaar/gear/branch/master/graph/badge.svg?token=MRowdXaujg)](https://codecov.io/gl/gnaar/gear)
[![pipeline status](https://gitlab.com/gnaar/gear/badges/master/pipeline.svg)](https://gitlab.com/gnaar/gear/commits/master)
[![Python versions](https://img.shields.io/pypi/pyversions/gnar-gear.svg)](https://pypi.python.org/pypi/gnar-gear)
[![PyPI version](https://badge.fury.io/py/gnar-gear.svg)](https://badge.fury.io/py/gnar-gear)

Sets up a powerful Flask-based Python service with two lines of code:

``` python
from gnar_gear import GnarApp

...

GnarApp('my_gnarly_app', production=True, port=80).run()
```

## Installation

``` bash
pip3 install gnar-gear
```

## Feature List

- Flask app with auto blueprint registration
- [Bjoern WSGI Server](https://github.com/jonashaag/bjoern)
- Why Bjoern? Check out [these benchmarks!](https://blog.appdynamics.com/engineering/a-performance-analysis-of-python-wsgi-servers-part-2/)
- And also [these benchmarks](https://github.com/kubeup/python-wsgi-benchmark)
- Postgres database connection via [Postgres.py](https://postgres-py.readthedocs.io/en/latest/#tutorial)
- SES client connection via [Boto 3](https://boto3.readthedocs.io/en/latest/)
- JWT configuration via [Flask-JWT-Extended](http://flask-jwt-extended.readthedocs.io/en/latest/)
- [argon2_cffi.PasswordHasher](https://argon2-cffi.readthedocs.io) instance
- `Argon2` was the winner of the [2015 Password Hashing Competition](https://password-hashing.net)
- Logger configuration
- Error handler with traceback
- Overridable and extendable class-based design

## Requirements

### Bjoern

`Bjoern` requires `libev` (high performance event loop)
- Install `libev` with `brew install libev` on Mac, or find your platform-specific installation command [here](https://github.com/jonashaag/bjoern/wiki/Installation#libev)

### Application Structure

`GnarApp` expects to be instantiated in `main.py` at `<top-level-module>/app`, i.e. the minimum app folder structure is
```
+ <top-level-module>
+ app
main.py
__init__.py
```
It is recommended (not required) to place your apis in segregated folders under `app` and the tests in a `test` folder under the `<top-level-module>`, e.g.
```
+ <top-level-module>
+ app
+ admin
apis.py
constants.py
services.py
+ user
apis.py
constants.py
services.py
__init__.py
main.py
+ test
constants.py
test_app.py
__init__.py
```

### Blueprints

Each [Flask `Blueprint`](http://flask.pocoo.org/docs/1.0/blueprints/) must be assigned to a global-level `blueprint` variable in its module, e.g.

``` python
from flask import Blueprint, jsonify


api_name = 'user'
url_prefix = '/{}'.format(api_name)

blueprint = Blueprint(api_name, __name__, url_prefix=url_prefix)
^^^^^^^^^

@blueprint.route('/get', methods=['GET'])
def user_get():
return jsonify({'status': 'ok'})
```

By default, the `GnarApp` picks up every `blueprint` in an auto-scan of the application code.

## Overview

The `GnarApp` class provides a highly configurable, feature-rich, production-ready Flask-based app.

### Parameters

#### Args (required)

- *name*: The name of the application's top-level module
- *production*: Boolean flag indicating whether or not the build is in production mode
- *port*: The port to bind to the WSGI server

#### Kwargs (optional)

- *env_prefix*: Environment variable prefix (defaults to `GNAR`)
- *log_level*: Log level override - see [configure_logger](#configure_logger) for log level overview
- *blueprint_modules*: List of modules to find Flask blueprints (default is auto-scan)
- *no_db*: Boolean flag - specify `True` if the app does not need a Postgres connection
- *no_jwt*: Boolean flag - specify `True` if the app does not use JWT headers (i.e. non-api services)

### Overridable Behavior

`GnarApp.run` simply calls a set of steps in the class. Here is an example of how to override any of the steps:

``` python
def postconfig():
log.info('My Postconfig Step!')

ga = GnarApp('my_gnarly_app', production=True, port=80)
ga.postconfig = postconfig
ga.run()
```

### Run Steps
The run steps rely on a set of [environment variables](#environment-variables) which use a configurable prefix
(i.e. the `env_prefix` parameter). The default `env_prefix` is `GNAR`. An example of a Gnar environment variable
using a custom prefix is `GnarApp( ..., env_prefix='MY_APP')` and then instead of reading `GNAR_LOG_LEVEL`, the
`configure_logger` step will read `MY_APP_LOG_LEVEL`.

#### preconfig

- No default behavior - provided as an optional initial step in the app configuration.

#### configure_flask

- Attaches a `Flask` instance to the Gnar app.

#### configure_logger

- Attaches the root logger to `sys.stdout`.
- Sets the logging level to the first defined:
- `log_level` parameter
- `GNAR_<app name>_LOG_LEVEL` environment variable, e.g. `GNAR_MY_GNARLY_APP_LOG_LEVEL`
- `GNAR_LOG_LEVEL` environment variable
- `INFO`
- Reminder: [Valid settings](https://docs.python.org/2/library/logging.html#logging-levels) (in increasing order of severity) are `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
- Sets the log format to the first defined:
- `GNAR_LOG_FORMAT`
- `'%(asctime)s %(levelname)-8s %(name)s:%(lineno)d %(message)s'`, e.g.:
<pre>
2018-07-09 15:41:46.420 INFO gear.gnar_app:75 Logging at INFO
</pre>
- Sets the log format `default_msec_format` to the first defined:
- `GNAR_LOG_FORMAT_MSEC`
- `'%s.%03d'` (e.g. `.001`)

#### configure_argon2

- Attaches an `argon2_cffi.PasswordHasher` instance to the Gnar app.
- Reads the following environment variables `[TYPE: DEFAULT]` to [pass into the Argon2 instance](https://argon2-cffi.readthedocs.io/en/stable/api.html):
- `GNAR_ARGON2_TIME_COST`: `[INT: 2]` Number of iterations to perform
- `GNAR_ARGON2_MEMORY_COST`: `[INT: 512]` Amount of memory (in KB) to use
- `GNAR_ARGON2_PARALLELISM`: `[INT: 2]` Number of parallel threads (changes the resulting hash value)
- `GNAR_ARGON2_HASH_LEN`: `[INT: 16]` Length of the hash in bytes
- `GNAR_ARGON2_SALT_LEN`: `[INT: 16]` Length of random salt to be generated for each password in bytes
- `GNAR_ARGON2_ENCODING`: `[STR: 'utf-8']` Encoding to use when a string is passed into `hash` or `verify`
- Note [from the docs](https://argon2-cffi.readthedocs.io/en/stable/parameters.html):
> Only tweak these if you’ve determined using CLI that these defaults are too slow or too fast for your use case.
- To hash a password using `Argon2`:
``` python
from <top-level-module>.main import app
hash = app.generate_password_hash(<plain text password>)
# OR
hash = app.argon2.hash(<plain text password>)
```
Note that this creates a [randomly salted, memory-hard hash using the Argon2i algorithm](https://argon2-cffi.readthedocs.io/en/stable/parameters.html).
- To validate a password with `Argon2`:
``` python
from <top-level-module>.main import app
is_valid = app.check_password_hash(<password hash from database>, <plain text password>)
# OR
is_valid = app.argon2.verify(<password hash from database>, <plain text password>)
```
Note that `app.argon2.verify` [raises an exception](https://argon2-cffi.readthedocs.io/en/stable/faq.html) if the password is invalid whereas `app.check_password_hash` does not.

#### configure_database

- Creates a Postgres database connection and attaches it to the Gnar app
- Reads the following environment variables to set the `host`, `dbname`, `user`, `password` connection string parameters, respectively:
- `GNAR_PG_ENDPOINT`
- `GNAR_PG_DATABASE`
- `GNAR_PG_USERNAME`
- `GNAR_PG_PASSWORD`
- Note: The [Postgres API](https://postgres-py.readthedocs.io/en/latest/#tutorial) primarily consists of `run`, `one`, and `all`

#### attach_instance

- Attaches the `GnarApp` instance to the `app.main` module. This enables easy access to the Gnar app from anywhere in the application using
``` python
from <top-level-module>.main import app
```
- The `GnarApp`'s runtime assets are `db`, `argon2`, `flask`, `check_password_hash`, `generate_password_hash`, and `get_ses_client`
- For example, to fetch one result (or `None`) from the database:
``` python
app.db.one("SELECT * FROM foo WHERE bar='buz'")
```

#### configure_blueprints

- By default, `GnarApp` [auto-scans every Python module](#blueprints) under the `app` folder for blueprints.
- Each [Flask `Blueprint`](http://flask.pocoo.org/docs/1.0/blueprints/) must be assigned to a global-level `blueprint` variable in its module.
- If you prefer to skip the auto-scan, you can provide a list (or single string) of blueprint modules.
- Each item in the list of module names may use one of two formats:
- Without a `.` in the module name: `GnarApp` will look for the module in `<top-level-module>.app.<module name>.apis`
- With a `.` in the module: `GnarApp` will look for the module in `<top-level-module>.app.<module name>`

#### configure_errorhandler

- Defines a generic (Exception-level) Flask error handler which:
- Logs the error message and its `traceback` (format_exec)
- Returns a 200-level json response containing `{"error": <error message>, "traceback": <traceback>}`

#### configure_jwt

- Sets the Flask `JWT_SECRET_KEY` variable to the value of the `GNAR_JWT_SECRET_KEY` environment variable.
- Sets the Flask `JWT_ACCESS_TOKEN_EXPIRES` variable to the value of the `GNAR_JWT_ACCESS_TOKEN_EXPIRES_MINUTES` environment variable (default 15 mins).
- Attaches a [JWTManager](http://flask-jwt-extended.readthedocs.io/en/latest/_modules/flask_jwt_extended/jwt_manager.html#JWTManager) instance to the `GnarApp`.
- Defines functions for `expired_token_loader`, `invalid_token_loader`, and `unauthorized_loader` which return meaningful error messages as 200-level json responses containing `{"error": <error message>}`.

#### configure_after_request

- Adds a JWT Authorization header (Bearer token) to responses which received a valid JWT token in the request.
- In non-production mode, adds CORS headers to the response (so that you don't need to bother with circumventing CORS in development).

#### postconfig

- No default behavior - provided as an optional initial step in the app configuration.

### Runtime Functionality

#### generate_password_hash / check_password_hash

- Convenience wrappers for `app.argon2.hash` and `app.argon2.verify`
- Usage:
``` python
from <top-level-module>.main import app
hash = app.generate_password_hash(<plain text password>)
is_valid = app.check_password_hash(<password hash from database>, <plain text password>)
```

#### get_ses_client

- Exposed as runtime functionality (as opposed to creating the client at initialization) because AWS will close the client after a short period of time
- Returns an SES connection (using boto3)
- Reads the following environment variables to set the `region_name`, `aws_access_key_id`, and `aws_secret_access_key` parameters of the `boto3.client` call, respectively:
- `GNAR_SES_REGION_NAME`
- `GNAR_SES_ACCESS_KEY_ID`
- `GNAR_SES_SECRET_ACCESS_KEY`
- Usage:
``` python
from <top-level-module>.main import app
app.get_ses_client().send_email( ... )
```
- See the [`Boto 3 Docs`](https://boto3.readthedocs.io/en/latest/reference/services/ses.html#SES.Client.send_email) for the `send_email` request syntax.

### Environment Variables

- The environment variables (with configurable prefix) used by `GnarApp` are:
- `GNAR_ARGON2_ENCODING`
- `GNAR_ARGON2_HASH_LEN`
- `GNAR_ARGON2_MEMORY_COST`
- `GNAR_ARGON2_PARALLELISM`
- `GNAR_ARGON2_SALT_LEN`
- `GNAR_ARGON2_TIME_COST`
- `GNAR_JWT_SECRET_KEY`
- `GNAR_LOG_LEVEL`
- `GNAR_PG_DATABASE`
- `GNAR_PG_ENDPOINT`
- `GNAR_PG_PASSWORD`
- `GNAR_PG_USERNAME`
- `GNAR_SES_ACCESS_KEY_ID`
- `GNAR_SES_REGION_NAME`
- `GNAR_SES_SECRET_ACCESS_KEY`
- See the relevant sections above for details

---
<div align="center">Made with ❤ by a Canadian living in Redwood City, California | Keep it Rad, friends 🤙</div>

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gnar-gear-1.0.3.tar.gz (10.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gnar_gear-1.0.3-py3-none-any.whl (9.1 kB view details)

Uploaded Python 3

File details

Details for the file gnar-gear-1.0.3.tar.gz.

File metadata

  • Download URL: gnar-gear-1.0.3.tar.gz
  • Upload date:
  • Size: 10.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for gnar-gear-1.0.3.tar.gz
Algorithm Hash digest
SHA256 325d83fcbb11fea525ed73d37772d42702f464bbd79f700fae12aaa9f743136e
MD5 cd8be85fee6cca2811a20957d0ff367b
BLAKE2b-256 5874dce3659e5e19bb1dad66a77fa3fa0310cad12a46032380d9c4e7c71ee415

See more details on using hashes here.

File details

Details for the file gnar_gear-1.0.3-py3-none-any.whl.

File metadata

File hashes

Hashes for gnar_gear-1.0.3-py3-none-any.whl
Algorithm Hash digest
SHA256 c467f22841157658e3e3914c6918e634023d23abcc2ec792b4fedc9efbba18e0
MD5 34f02479c64592472613707659641ea5
BLAKE2b-256 0659d8acbff3b1b73a743f4bd8be15c3525a6a35e1635dae78d9edde7c10f56e

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page