Skip to content

Commit

Permalink
Merge pull request #15 from LogZEN/features/api-streams
Browse files Browse the repository at this point in the history
Added API for stream management
  • Loading branch information
reissmann committed Nov 5, 2014
2 parents ee09e18 + 5cbd188 commit 174c8a4
Show file tree
Hide file tree
Showing 7 changed files with 212 additions and 37 deletions.
23 changes: 15 additions & 8 deletions backend/src/logzen/db/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
from logzen.db import Entity, JSONDict, DAO



class Stream(Entity):
""" The database entity for streams.
Expand Down Expand Up @@ -57,39 +56,47 @@ class Stream(Entity):
nullable=False)



@export()
class Streams(DAO):
""" DAO for accessing stream entities.
"""

def getStream(self, name):
def getStreamByName(self, user, name):
try:
return self.session \
.query(Stream) \
.filter(Stream.user == user) \
.filter(Stream.name == name) \
.one()

except sqlalchemy.orm.exc.NoResultFound:
raise KeyError(name)


def getStreams(self):
""" Returns an iterator over all existing stream entities.
def getStreamsByUser(self, user):
""" Returns all existing stream entities for the passed user.
"""

return iter(self.session.query(Stream))
return self.session \
.query(Stream) \
.filter(Stream.user == user) \
.all()


def createStream(self, name):
def createStream(self, user, **kwargs):
""" Create a new stream entity.
All parameters are passed as-is to the entity to create. The
created entity is attached to the session and returned.
"""

stream = Stream(name=name)
stream = Stream(user=user,
**kwargs)

self.session.add(stream)

return stream


def deleteStream(self, stream):
self.session.delete(stream)
6 changes: 3 additions & 3 deletions backend/src/logzen/db/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class Users(DAO):
""" Accessor for user entities.
"""

def getUser(self, username):
def getUserByName(self, username):
""" Get a user entity with the given username.
If a user with such username exists, the user entity is returned.
Expand All @@ -95,15 +95,15 @@ def getUser(self, username):
raise KeyError(username)


def getVerifiedUser(self, username, password):
def getVerifiedUserByName(self, username, password):
""" Get a user entity with the given username and a matching password.
If a user with such username exists and the users password matches
the given one the user entity is returned - None otherwise.
"""

try:
user = self.getUser(username)
user = self.getUserByName(username)

except sqlalchemy.orm.exc.NoResultFound:
return None
Expand Down
12 changes: 7 additions & 5 deletions backend/src/logzen/web/api/admin/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def list(users):
def get(name,
users):
try:
user = users.getUser(name)
user = users.getUserByName(name)

return {'username': user.username,
'admin': user.admin}
Expand Down Expand Up @@ -76,22 +76,24 @@ def update(name,
request):
with session():
try:
user = users.getUser(name)
user.__init__(**request.data)
user = users.getUserByName(name)

except KeyError:
raise bottle.HTTPError(404, 'User not found: %s' % name)

user.__init__(**request.data)


@resource('/users/<name>', 'DELETE')
@require(users='logzen.db.users:Users')
def delete(name,
users):
with session():
try:
user = users.getUser(name)
users.deleteUser(user)
user = users.getUserByName(name)

except KeyError:
raise bottle.HTTPError(404, 'User not found: %s' % name)

users.deleteUser(user)

4 changes: 2 additions & 2 deletions backend/src/logzen/web/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def wrapper(*args, **kwargs):
self.logger.debug('Token validated with username: %s', username)

# Get the user object for the username
user = self.users.getUser(username=username)
user = self.users.getUserByName(username=username)
if user is None:
raise bottle.HTTPError(401, 'User does not exist: ' + username)

Expand Down Expand Up @@ -150,7 +150,7 @@ def install(api,
users='logzen.db.users:Users')
def login(request,
users):
user = users.getVerifiedUser(**request.data)
user = users.getVerifiedUserByName(**request.data)

if user is None:
raise bottle.HTTPError(401, 'Wrong username or password')
Expand Down
81 changes: 72 additions & 9 deletions backend/src/logzen/web/api/user/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,91 @@
import bottle

from require import *
from logzen.db import session
from logzen.web.api.user import resource


@resource('/streams', 'GET')
@require(user='logzen.web.api.auth:User')
def list(user):
return {key: {'name': stream.name,
'description': stream.description}
for key, stream
in user.streams.items()}
@require(user='logzen.web.api.auth:User',
streams='logzen.db.streams:Streams')
def list(user,
streams):
return {stream.name: {'description': stream.description,
'filter': stream.filter}
for stream
in streams.getStreamsByUser(user)}


@resource('/streams/<name>', 'GET')
@require(user='logzen.web.api.auth:User')
@require(user='logzen.web.api.auth:User',
streams='logzen.db.streams:Streams')
def get(name,
user):
user,
streams):
try:
stream = user.streams[name]
stream = streams.getStreamByName(user, name)

except KeyError:
raise bottle.HTTPError(404, 'Stream not found: %s' % name)

return {'name': stream.name,
'description': stream.description,
'filter': stream.filter}


@resource('/streams', 'POST',
schema={'type': 'object',
'properties': {'name': {'type': 'string'},
'description': {'type': 'string'},
'filter': {'type': 'object'}},
'required': ['name',
'filter']})
@require(user='logzen.web.api.auth:User',
streams='logzen.db.streams:Streams',
request='logzen.web.api:Request')
def create(user,
streams,
request):
with session():
streams.createStream(user, **request.data)


@resource('/streams/<name>', 'PUT',
schema={'type': 'object',
'properties': {'name': {'type': 'string'},
'description': {'type': 'string'},
'filter': {'type': 'object'}},
'required': ['name',
'filter']})
@require(user='logzen.web.api.auth:User',
streams='logzen.db.streams:Streams',
request='logzen.web.api:Request')
def update(name,
user,
streams,
request):
with session():
try:
stream = streams.getStreamByName(user, name)

except KeyError:
raise bottle.HTTPError(404, 'Stream not found: %s' % name)

stream.__init__(**request.data)


@resource('/streams/<name>', 'DELETE')
@require(user='logzen.web.api.auth:User',
streams='logzen.db.streams:Streams')
def delete(name,
user,
streams):
with session():
try:
stream = streams.getStreamByName(user, name)

except KeyError:
raise bottle.HTTPError(404, 'Stream not found: %s' % name)

streams.deleteStream(stream)

117 changes: 110 additions & 7 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import unittest
import json

from hamcrest import *

import werkzeug.test
import werkzeug.wrappers

import json

from require import *

from logzen.db import session



class ApiTestCase(unittest.TestCase):
testConnection = require('util:TestConnection')

Expand Down Expand Up @@ -42,7 +38,6 @@ def tearDown(self):
self.transaction.rollback()



class AuthenticationApiTestCase(ApiTestCase):
users = require('logzen.db.users:Users')

Expand Down Expand Up @@ -134,6 +129,114 @@ def testFetchingAccountInfoSucceeds(self):
assert_that(json.loads(resp.data.decode('utf8')), is_({'username': 'user'}))


class UserStreamsApiTestCase(ApiTestCase):
users = require('logzen.db.users:Users')
streams = require('logzen.db.streams:Streams')


def setUp(self):
super(UserStreamsApiTestCase, self).setUp()

with session():
self.user = self.users.createUser(username='user',
password='user')

self.client.post('/api/v1/token',
data=json.dumps({'username': 'user',
'password': 'user'}))


def testCreatingStreamSucceeds(self):
resp = self.client.post('/api/v1/user/streams',
data=json.dumps({'name': 'test',
'description': 'For testing purposes only',
'filter': {'foo': 42,
'bar': 23}}))

assert_that(resp.status_code, is_(200))

with session():
user = self.users.getUserByName('user')

assert_that(user.streams, has_length(1))
assert_that(user.streams, has_items('test'))


def testDeletingStreamSucceeds(self):
with session():
self.streams.createStream(user=self.user,
name='test',
filter={})

resp = self.client.delete('/api/v1/user/streams/test')

assert_that(resp.status_code, is_(200))

with session():
user = self.users.getUserByName('user')

assert_that(user.streams, is_(empty()))


def testListingStreamsSucceeds(self):
with session():
self.streams.createStream(user=self.user,
name='test1',
filter={})
self.streams.createStream(user=self.user,
name='test2',
filter={})
self.streams.createStream(user=self.user,
name='test3',
filter={})

resp = self.client.get('/api/v1/user/streams')

assert_that(resp.status_code, is_(200))
assert_that(json.loads(resp.data.decode('utf8')), is_({'test1': {'description': '',
'filter': {}},
'test2': {'description': '',
'filter': {}},
'test3': {'description': '',
'filter': {}}}))


def testFetchingStreamSucceeds(self):
with session():
self.streams.createStream(user=self.user,
name='test',
filter={})

resp = self.client.get('/api/v1/user/streams/test')

assert_that(resp.status_code, is_(200))
assert_that(json.loads(resp.data.decode('utf8')), is_({'name': 'test',
'description': '',
'filter': {}}))


def testUpdatingStreamSucceeds(self):
with session():
self.streams.createStream(user=self.user,
name='test',
filter={})

resp = self.client.put('/api/v1/user/streams/test',
data=json.dumps({'name': 'toast',
'description': 'x',
'filter': {'foo': 'bar'}}))

assert_that(resp.status_code, is_(200))

with session():
user = self.users.getUserByName('user')
stream = user.streams['toast']

assert_that(stream.name, is_('toast'))
assert_that(stream.description, is_('x'))
assert_that(stream.filter, is_({'foo': 'bar'}))


class AdminApiTestCase(ApiTestCase):
users = require('logzen.db.users:Users')

Expand Down Expand Up @@ -225,7 +328,7 @@ def testCreatingUserSucceeds(self):
assert_that(resp.status_code, is_(200))
assert_that(resp.data, is_(b''))

user = self.users.getUser('marvin')
user = self.users.getUserByName('marvin')

assert_that(user, is_(not_none()))
assert_that(user, has_properties(username='marvin',
Expand Down
Loading

0 comments on commit 174c8a4

Please sign in to comment.