The gnarliest gear in the world 🤙
Project description
Gnar Gear: Gnarly Python Apps
Part of Project Gnar: base • gear • piste • off-piste • edge • powder • genesis • patrol
Get started with Project Gnar on
Gnar Gear sets up a powerful Flask-based Python service with two lines of code:
from gnar_gear import GnarApp
...
GnarApp('my_gnarly_app', production=True, port=80).run()
Installation
pip3 install gnar-gear
Feature List
- Flask app with auto blueprint registration
- Flask WSGI Development Server
- Bjoern WSGI Production Server
- Why Bjoern? Check out these benchmarks!
- And also these benchmarks
- Postgres database connection via Postgres.py
- SES client connection via Boto 3
- JWT configuration via Flask-JWT-Extended
- Peer requests - HTTP requests to other microservices in the app
- External requests - Convenience wrapper around requests
- SQS Message Polling and Sending
- argon2_cffi.PasswordHasher instance
Argon2was the winner of the 2015 Password Hashing Competition
- Logger configuration
- Error handler with traceback
- Overridable and extendable class-based design
Development Mode
- Flask WSGI server in fault-tolerant debug (watch & reload) mode
- The module loader logs the stack trace of any modules that fail to load
- If a module fails to load, the app continues to watch for file changes
- CORS "disabled" (
Access-Controlresponse headers set), so you don't need to use other means to circumvent CORS
Production Mode
- Bjoern WSGI server
- CORS "enabled" (
Access-Controlresponse headers are not set)
Requirements
Bjoern
Bjoern requires libev (high performance event loop)
- Install
libevwithbrew install libevon Mac, or find your platform-specific installation command here
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 must be assigned to a global-level blueprint variable in its module, e.g.
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 for log level overview
- blueprint_modules: List of modules to find Flask blueprints (default is auto-scan)
- see configure_blueprints for blueprints overview
- sqs: List (or single dict) of SQS queues to poll
- see configure_sqs_polls for SQS polling overview
- no_db: Boolean flag - specify
Trueif the app does not need a Postgres connection - no_jwt: Boolean flag - specify
Trueif 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:
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 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
Flaskinstance to the Gnar app.
configure_logger
- Attaches the root logger to
sys.stdout. - Sets the logging level to the first defined:
log_levelparameterGNAR_<app name>_LOG_LEVELenvironment variable, e.g.GNAR_MY_GNARLY_APP_LOG_LEVELGNAR_LOG_LEVELenvironment variableINFO- Reminder: Valid settings (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.:
2018-07-09 15:41:46.420 INFO gear.gnar_app:75 Logging at INFO
- Sets the log format
default_msec_formatto the first defined:GNAR_LOG_FORMAT_MSEC'%s.%03d'(e.g..001)
configure_argon2
-
Attaches an
argon2_cffi.PasswordHasherinstance to the Gnar app. -
Reads the following environment variables
[TYPE: DEFAULT]to pass into the Argon2 instance:GNAR_ARGON2_TIME_COST:[INT: 2]Number of iterations to performGNAR_ARGON2_MEMORY_COST:[INT: 512]Amount of memory (in KB) to useGNAR_ARGON2_PARALLELISM:[INT: 2]Number of parallel threads (changes the resulting hash value)GNAR_ARGON2_HASH_LEN:[INT: 16]Length of the hash in bytesGNAR_ARGON2_SALT_LEN:[INT: 16]Length of random salt to be generated for each password in bytesGNAR_ARGON2_ENCODING:[STR: 'utf-8']Encoding to use when a string is passed intohashorverify
-
Note from the docs:
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: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.
-
To validate a password with
Argon2: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.verifyraises an exception if the password is invalid whereasapp.check_password_hashdoes 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,passwordconnection string parameters, respectively:GNAR_PG_ENDPOINTGNAR_PG_DATABASEGNAR_PG_USERNAMEGNAR_PG_PASSWORD
- Note: The Postgres API primarily consists of
run,one, andall
attach_instance
-
Attaches the
GnarAppinstance to theapp.mainmodule. This enables easy access to the Gnar app from anywhere in the application usingfrom <top-level-module>.main import app
-
The
GnarApp's runtime assets aredb,argon2,flask,check_password_hash,generate_password_hash, andget_ses_client -
For example, to fetch one result (or
None) from the database:app.db.one("SELECT * FROM foo WHERE bar='buz'")
configure_blueprints
- By default,
GnarAppauto-scans every Python module under theappfolder for blueprints. - Each Flask
Blueprintmust be assigned to a global-levelblueprintvariable 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:GnarAppwill look for the module in<top-level-module>.app.<module name>.apis - With a
.in the module:GnarAppwill look for the module in<top-level-module>.app.<module name>
- Without a
- Each item in the list of module names may use one of two formats:
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>}
- Logs the error message and its
configure_jwt
- Sets the Flask
JWT_SECRET_KEYvariable to the value of theGNAR_JWT_SECRET_KEYenvironment variable. - Sets the Flask
JWT_ACCESS_TOKEN_EXPIRESvariable to the value of theGNAR_JWT_ACCESS_TOKEN_EXPIRES_MINUTESenvironment variable (default 15 mins). - Attaches a JWTManager instance to the
GnarApp. - Defines functions for
expired_token_loader,invalid_token_loader, andunauthorized_loaderwhich return meaningful error messages as 200-level json responses containing{"error": <error message>}.
configure_sqs_polls
-
Configures a set of polls to run at specified intervals to receive messages from SQS queues.
-
Specified via the
sqskwarg as a list of dicts (or a single dict) with a set of required (*) and optional properties. See the AWS ReceiveMessage docs for more details on the optional properties:- * queue_name: The name of the SQS queue - the queue URL is retrieved from AWS with this name
- * callback: The function to call with each received message. The message will be deleted from the queue unless the callback function returns
False. - interval_seconds: The interval (in seconds) between receive message requests - default 60
- attribute_names: A list of attributes to return with each message
- max_number_of_messages: The maximum number of messages to return with each request - 1 to 10, default 1
- message_attribute_names: Metadata to include with the message - more details
- receive_request_attempt_id: The token used for deduplication of ReceiveMessage calls (FIFO queues only)
- visibility_timeout: The duration (in seconds) that the received messages are hidden from subsequent retrieve requests
- wait_time_seconds: The duration (in seconds) for which the call waits for a message to arrive in the queue before returning
-
Each message received by the callback includes the following properties. See the AWS Message docs for more details on these properties:
- Body: Message contents
- Attributes: Map of requested attributes
- MD5OfBody: MD5 Digest of the message body
- MD5OfMessageAttributes: MD5 digest of the message attribute string
- MessageAttributes: Map of custom message metadata
-
Notes:
- Messages are processed sequentially in the order they are received
- The
callbackblocks message processing until it's complete - After all received messages are processed, the queue is immediately checked for new messages
- Once the
receive_messagecall returns no messages, aTimerwith the specifiedinterval_secondsstarts the next loop
-
Reads the following environment variables to set the
region_name,aws_access_key_id, andaws_secret_access_keyparameters of theboto3.clientcall, respectively:GNAR_SQS_REGION_NAMEGNAR_SQS_ACCESS_KEY_IDGNAR_SQS_SECRET_ACCESS_KEY
-
Example:
def receive_sqs_message(message): redis_connection.set(message['MessageId'], message['Body'], 3600) sqs = {'queue_name': 'gnar-queue', 'callback': receive_sqs_message} GnarApp('piste', production, port, sqs=sqs).run()
configure_after_request
- Adds a JWT Authorization header (Bearer token) to responses which received a valid JWT token in the request.
- In development mode, adds CORS headers to the response (so that you don't need to bother circumventing CORS).
postconfig
- No default behavior - provided as an optional initial step in the app configuration.
Runtime Functionality
peer
-
Handles requests to other microservices in a Kubernetes app.
-
Usage:
from <top-level-module>.main import app peer = app.peer(<< service name >>)
-
Requires environment variables in the form:
- production:
<< SERVICE >>_SERVICE_PORT, e.g.PISTE_SERVICE_PORT - development:
GNAR_<< SERVICE >>_SERVICE_PORT, e.g.GNAR_PISTE_SERVICE_PORT
- production:
-
In production, the environment variable is automatically set by Kubernetes
-
In development, the environment variable should point to another Gnar Gear app on localhost on a separate port
-
The environment variable value must be in the form
<< ip address >>:<< port >>, e.g.export GNAR_PISTE_SERVICE_PORT='127.0.0.1:9401'
peer Methods
-
Each peer method accepts all the
kwargsof the underlyingrequestsmethod in addition to anauto_authproperty.- If
auto_authisTrueand the current route is protected, an Authorization header is added to the request. - Both microservices must use the same
GNAR_JWT_SECRET_KEYfor the authorization to work.
- If
-
Uses requests under the hood:
- request(method, path, **kwargs): Constructs and sends a request
- head(path, **kwargs): Sends a HEAD request
- get(path, params=None, **kwargs): Sends a GET request
- post(path, data=None, json=None, **kwargs): Sends a POST request
- put(path, data=None, **kwargs): Sends a PUT request
- patch(path, data=None, **kwargs): Sends a PATCH request
- delete(path, **kwargs): Sends a DELETE request
-
Example:
from <top-level-module>.main import app response = app.peer('piste').post('login', {'email': 'ic3b3rg@gmail.com'})
external
-
Convenience wrapper around requests
-
Example:
from <top-level-module>.main import app response = app.external.get('https://api.pwnedpasswords.com/range/5ce7d')
generate_password_hash / check_password_hash
-
Convenience wrappers for
app.argon2.hashandapp.argon2.verify -
Usage:
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, andaws_secret_access_keyparameters of theboto3.clientcall, respectively:GNAR_SES_REGION_NAMEGNAR_SES_ACCESS_KEY_IDGNAR_SES_SECRET_ACCESS_KEY
-
Usage:
from <top-level-module>.main import app app.get_ses_client().send_email( ... )
-
See the
Boto 3 Docsfor thesend_emailrequest syntax.
send_sqs_message
-
Sends a message to an SQS queue.
-
Args (required):
- queue_name: The name of the SQS queue - the queue URL is retrieved from AWS with this name
- message_body: Message contents
-
Kwargs (optional) - See the AWS SendMessage docs for more details on these properties:
- delay_seconds: The length of time, in seconds, for which to delay a specific message
- message_attributes: Metadata to include with the message
- message_deduplication_id: The token used for deduplication of sent messages (FIFO queues only)
- message_group_id: The tag that specifies that a message belongs to a specific message group (FIFO queues only)
-
Returns a dict with the following attributes:
- MessageId: Unique message identifier
- MD5OfMessageAttributes: MD5 digest of the message attribute string
- MD5OfMessageBody: MD5 digest of the message body
- SequenceNumber: Unique, non-consecutive, 128 bit string (FIFO queues only)
-
Reads the following environment variables to set the
region_name,aws_access_key_id, andaws_secret_access_keyparameters of theboto3.clientcall, respectively:GNAR_SQS_REGION_NAMEGNAR_SQS_ACCESS_KEY_IDGNAR_SQS_SECRET_ACCESS_KEY
-
Example:
from <top-level-module>.main import app message = app.send_sqs_message('gnar-queue', 'The gnarliest gear in the world 🤙')
Environment Variables
- The environment variables (with configurable prefix) used by
GnarAppare:GNAR_ARGON2_ENCODINGGNAR_ARGON2_HASH_LENGNAR_ARGON2_MEMORY_COSTGNAR_ARGON2_PARALLELISMGNAR_ARGON2_SALT_LENGNAR_ARGON2_TIME_COSTGNAR_JWT_SECRET_KEYGNAR_LOG_LEVELGNAR_PG_DATABASEGNAR_PG_ENDPOINTGNAR_PG_PASSWORDGNAR_PG_USERNAMEGNAR_SES_ACCESS_KEY_IDGNAR_SES_REGION_NAMEGNAR_SES_SECRET_ACCESS_KEYGNAR_SQS_REGION_NAMEGNAR_SQS_ACCESS_KEY_IDGNAR_SQS_SECRET_ACCESS_KEY
- See the relevant sections above for details
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gnar-gear-1.1.5.tar.gz.
File metadata
- Download URL: gnar-gear-1.1.5.tar.gz
- Upload date:
- Size: 20.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.21.0 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/3.6.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4be755217fcabbbae7a752bb57f2dc8020acdd83d6904f6d3b2b283c4c603787
|
|
| MD5 |
5be59b8834e0429ef6fa7cefc3864ce1
|
|
| BLAKE2b-256 |
ac6ff58ffb3809397ec8cbb01f7f61a3fcd5d931dda4e0844c2a9904e1aecc08
|
File details
Details for the file gnar_gear-1.1.5-py3-none-any.whl.
File metadata
- Download URL: gnar_gear-1.1.5-py3-none-any.whl
- Upload date:
- Size: 13.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/1.11.0 pkginfo/1.4.2 requests/2.21.0 setuptools/40.2.0 requests-toolbelt/0.8.0 tqdm/4.23.4 CPython/3.6.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
19ba3ffd753c84372f8aad18428c49dba0c6507a86783af1b2978ea42565c2a6
|
|
| MD5 |
206d918fa31fc10872901d2d6aa28142
|
|
| BLAKE2b-256 |
946016f54a1a7d314e05f9fdbce7426622a8601c6b67a5e8a34027ff09dcea64
|