Skip to content

Commit

Permalink
update database to use asyncpgsa
Browse files Browse the repository at this point in the history
  • Loading branch information
nhumrich committed Aug 23, 2016
1 parent 43ca369 commit f939fac
Show file tree
Hide file tree
Showing 15 changed files with 183 additions and 308 deletions.
2 changes: 1 addition & 1 deletion alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[alembic]
# path to migration scripts
script_location = alembic
script_location = migrations

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ def upgrade():


def downgrade():
op.drop_table('employees')
op.drop_table('roles')
op.drop_table('toggles')
op.drop_table('features')
op.drop_table('environments')
op.drop_table('toggles')
op.drop_table('squads')
File renamed without changes.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
asyncpgsa
aiohttp
sqlalchemy
alembic
Expand Down
19 changes: 0 additions & 19 deletions shippable.yml

This file was deleted.

7 changes: 4 additions & 3 deletions tests/api/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- name: 'test get envs'
- url: '/api/envs'
- validators:
- compare: {jmespath: 'envs[0].name', expected: 'dev'}
- compare: {jmespath: 'envs[0].name', expected: 'Production'}

- test:
- name: 'invalid json'
Expand Down Expand Up @@ -65,7 +65,7 @@
- name: 'get env toggles'
- url: '/api/envs/bob2/toggles?feature=bobbytables'
- validators:
- compare: {jmespath: "bobbytables", expected: "OFF"}
- compare: {jmespath: "bobbytables", expected: False}

- test:
- name: 'turn toggle on'
Expand Down Expand Up @@ -102,7 +102,8 @@
- name: 'get multiple env toggles'
- url: '/api/envs/bob2/toggles?feature=bobbytables&feature=bobbytables2'
- validators:
- compare: {jmespath: "bobbytables", expected: "OFF"}
- compare: {jmespath: "bobbytables2", expected: False}
- compare: {jmespath: "bobbytables", expected: True}


# test deletes and clean up
Expand Down
81 changes: 55 additions & 26 deletions tmeister/core.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
import os
import json

import asyncio
import aiogithubauth
from aiohttp import web
import aiohttp_autoreload
from aiohttp_index import IndexMiddleware
import aiogithubauth
import json
import os
from asyncpgsa import pg

from . import toggles, features, environments

debug = True
local_dev = os.getenv('IS_LOCAL', 'false').lower() == 'true'
gh_id = os.getenv('GITHUB_ID')
gh_secret = os.getenv('GITHUB_SECRET')
gh_org = os.getenv('GITHUB_ORG')
cookie_name = os.getenv('COOKIE_NAME', 's3githubauth')
cookie_key = os.getenv('COOKIE_KEY')
if (not local_dev) and None in (gh_id, gh_secret, gh_org):
DEBUG = True
LOCAL_DEV = os.getenv('IS_LOCAL', 'false').lower() == 'true'
GH_ID = os.getenv('GITHUB_ID')
GH_SECRET = os.getenv('GITHUB_SECRET')
GH_ORG = os.getenv('GITHUB_ORG')
COOKIE_NAME = os.getenv('COOKIE_NAME', 's3githubauth')
COOKIE_KEY = os.getenv('COOKIE_KEY')
POSTGRES_URL = os.getenv('DATABASE_URL', 'localhost')
POSTGRES_USERNAME = os.getenv('DATABASE_USER', 'postgres')
POSTGRES_PASSWORD = os.getenv('DATABASE_PASS', 'password')
POSTGRES_DB_NAME = os.getenv('DATABASE_DB_NAME', 'postgres')
POSTGRES_MIN_POOL_SIZE = os.getenv('DATABASE_MIN_POOL_SIZE', 2)
POSTGRES_MAX_POOL_SIZE = os.getenv('DATABASE_MAX_POOL_SIZE', 4)


if (not LOCAL_DEV) and None in (GH_ID, GH_SECRET, GH_ORG):
raise ValueError('GITHUB_ID, GITHUB_SECRET or GITHUB_ORG'
' environment variables are missing')

Expand All @@ -29,18 +39,34 @@ async def middleware_handler(request):
return middleware_handler


async def init(loop):
app = web.Application(loop=loop, middlewares=[
async def async_setup(loop):
await pg.init(
user=POSTGRES_USERNAME,
password=POSTGRES_PASSWORD,
host=POSTGRES_URL,
database=POSTGRES_DB_NAME,
loop=loop,
min_size=POSTGRES_MIN_POOL_SIZE,
max_size=POSTGRES_MAX_POOL_SIZE,
)


def init():
loop = asyncio.get_event_loop()
loop.run_until_complete(async_setup(loop))

app = web.Application(middlewares=[
error_middleware,
IndexMiddleware()])
if not local_dev:

if not LOCAL_DEV:
aiogithubauth.add_github_auth_middleware(
app,
github_id=gh_id,
github_secret=gh_secret,
github_org=gh_org,
cookie_name=cookie_name,
cookie_key=cookie_key,
github_id=GH_ID,
github_secret=GH_SECRET,
github_org=GH_ORG,
cookie_name=COOKIE_NAME,
cookie_key=COOKIE_KEY,
whitelist_handlers=[toggles.get_toggle_states_for_env],
api_unauthorized=True
)
Expand All @@ -56,19 +82,22 @@ async def init(loop):
app.router.add_route('POST', '/api/envs', environments.add_env)
app.router.add_route('DELETE', '/api/envs/{name}', environments.delete_env)
app.router.add_static('/', os.path.dirname(__file__) + '/static')

handler = app.make_handler(debug=debug)
await loop.create_server(handler, '0.0.0.0', 8445)
print('======= Server running at :8445 =======')
return app


def main():
# setup
app = init()
loop = asyncio.get_event_loop()

loop.run_until_complete(init(loop))
handler = app.make_handler(debug=DEBUG)
loop.run_until_complete(
loop.create_server(handler, '0.0.0.0', 8445)
)
print('======= Server running at :8445 =======')

if debug:
if DEBUG:
import aiohttp_autoreload
print('debug enabled, auto-reloading enabled')
aiohttp_autoreload.start()

Expand Down
125 changes: 59 additions & 66 deletions tmeister/dataaccess.py
Original file line number Diff line number Diff line change
@@ -1,129 +1,122 @@
"""
Right now this is just a bunch of static objects in memory. Later, this should
use an actual database
"""
import asyncio
from collections import defaultdict
from .db import DB, KeyAlreadyExistsError
__toggles = []
__envs = []
__switches = defaultdict(dict)
from asyncpgsa import pg

from . import db

OFF_STATE = 0
ON_STATE = 1
ROLLING_STATE = 4

db = DB()


async def add_env(env_name):
await db.go(db.environments.insert().values(name=env_name))
await pg.fetchval(db.environments.insert().values(name=env_name))
return {'name': env_name}


async def get_envs(*, env_list=None):
async def get_envs_(envs):
return [row.name for row in envs]

query = db.environments.select()
if env_list:
query = query.where(db.environments.c.name.in_(env_list))

result = await db.go(query, callback=get_envs_)
return result
envs = await pg.fetch(query)
return [row.name for row in envs]


async def get_features(*, feature_list=None):
async def get_features_(features):
return [row.name for row in features]

query = db.features.select()
if feature_list:
query = query.where(db.features.c.name.in_(feature_list))

result = await db.go(query,
callback=get_features_)
return result
features = await pg.fetch(query)
return [row.name for row in features]


async def add_feature(feature_name):
await db.go(db.features.insert().values(name=feature_name))
await pg.fetchval(db.features.insert().values(name=feature_name))
return {'name': feature_name}


async def delete_feature(feature_name):
await db.go(db.features.delete().where(db.features.c.name == feature_name))
await pg.fetchval(
db.features.delete()
.where(db.features.c.name == feature_name))


async def delete_env(env_name):
await db.go(db.environments.delete()
.where(db.environments.c.name == env_name))
await pg.fetchval(db.environments.delete()
.where(db.environments.c.name == env_name))

await db.go(db.toggles.delete().where(db.toggles.c.env == env_name))
await pg.fetchval(db.toggles.delete()
.where(db.toggles.c.env == env_name))

async def get_toggle_states_for_env(env, list_of_features):
async def _get_toggle_states(toggles):
return {row.feature: row.state for row in toggles}

query = db.toggles.select()\
.where(db.toggles.c.env == env)\
async def get_toggle_states_for_env(env, list_of_features):
query = db.toggles.select() \
.where(db.toggles.c.env == env) \
.where(db.toggles.c.feature.in_(list_of_features))

results = await db.go(query, callback=_get_toggle_states)
return results
toggles = await pg.fetch(query)
return {row.feature: row.state == 'ON' for row in toggles}


async def _get_toggles_(toggles):
return [{'toggle': {'env': row.env, 'feature': row.feature, 'state': row.state}}
for row in toggles]
def _transform_toggles(toggles):
return [
{'toggle':
{'env': row.env,
'feature': row.feature,
'state': row.state}}
for row in toggles]

async def set_toggle_state(env, feature, state):

results = await db.go(db.toggles
.select()
.where(db.toggles.c.feature == feature)
.where(db.toggles.c.env == env),
callback=_get_toggles_)
async def set_toggle_state(env, feature, state):
results = await pg.fetch(
db.toggles.select()
.where(db.toggles.c.feature == feature)
.where(db.toggles.c.env == env))

results = _transform_toggles(results)
if not results:
if state == 'ON':
await db.go(db.toggles
.insert()
.values(feature=feature, env=env, state='ON'))
await pg.insert(
db.toggles
.insert()
.values(feature=feature, env=env, state='ON'),
id_col_name=None
)
elif state == 'OFF':
await db.go(db.toggles
.delete()
.where(db.toggles.c.feature == feature)
.where(db.toggles.c.env == env))
await pg.fetchval(db.toggles
.delete()
.where(db.toggles.c.feature == feature)
.where(db.toggles.c.env == env))
return {
'toggle': {
'env': env,
'feature': feature,
'state': state
'state': state,
}
}


async def get_all_toggles():
query = """\
SELECT
environments.name as env,
features.name as feature,
CASE WHEN toggles.state IS NULL THEN 'OFF'
environments.name AS env,
features.name AS feature,
CASE
WHEN environments.name = 'dev' THEN 'ON'
WHEN toggles.state IS NULL THEN 'OFF'
ELSE toggles.state
END as state
END AS state
FROM environments
CROSS JOIN features
LEFT OUTER JOIN toggles ON feature = features.name AND env = environments.name;\
"""

print(query)
results = await db.go(query, callback=_get_toggles_)
toggles = await pg.fetch(query)
results = [
{'toggle': {'env': row.env,
'feature': row.feature,
'state': row.state}
}
for row in toggles
]
return {'toggles': results}


async def create_toggle(toggle_name):
await asyncio.sleep(0)
__toggles.append(toggle_name)


Loading

0 comments on commit f939fac

Please sign in to comment.